Merge lp:~veebers/juju-ci-tools/model_migration_controller_cleanup into lp:juju-ci-tools

Proposed by Christopher Lee
Status: Merged
Merged at revision: 1866
Proposed branch: lp:~veebers/juju-ci-tools/model_migration_controller_cleanup
Merge into: lp:juju-ci-tools
Diff against target: 402 lines (+179/-70)
5 files modified
assess_model_migration.py (+48/-19)
jujupy/tests/test_version_client.py (+4/-1)
jujupy/version_client.py (+11/-2)
tests/test_assess_cloud.py (+2/-2)
tests/test_assess_model_migration.py (+114/-46)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/model_migration_controller_cleanup
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+314790@code.launchpad.net

Commit message

Add test to ensure model stays operational after origin controller is destroyed (as per LP:1648063).

Description of the change

Add test to ensure model stays operational after origin controller is destroyed (as per LP:1648063).
Utilises existing test to re-use successfully migrated model.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. I have a suggestion inline.

review: Approve (code)
1844. By Christopher Lee

Cleanup multiline method call.

1845. By Christopher Lee

Merge trunk

1846. By Christopher Lee

Shift versioned ModelClients to have a split for 2.0/2.1 to ease test splits.

1847. By Christopher Lee

Update affected unit test.

1848. By Christopher Lee

Add 2.1 check as well as shuffle some things around to ease when I added new tests.

Revision history for this message
Christopher Lee (veebers) wrote :

Hmm, this branch ballooned out a bit with an attempt to make things nicer plus keeping tests up. Sorry about that.

I'm not sure I'm happy with my 2.1 detection, I'm tempted to instead parse the version string and compare that. Thoughts?

Revision history for this message
Aaron Bentley (abentley) wrote :

This test wants to know if the juju under test is defective. I am not sure that there is an advantage to testing a juju we know to be defective. Maybe we should just skip this test for juju 2.0.

If we do want to test 2.0, I think parsing the version is fine, and probably the best option.

A test already includes our expectations of how juju will behave. I don't think it's terrible for that test to include expectations of how different versions of juju behave.

I don't think having an empty class is much benefit. In general, our version classes allow us to achieve the same effect in two different ways (e.g. two different commandlines). This is not doing that.

If we did try to salvage the class-based approach, I would want there to be a difference between the classes. Perhaps they could have a member indicating capabilities, e.g. ModelClient2_0.MIGRATE_DESTROY_SOURCE = False; ModelClient2_1.MIGRATE_DESTROY_SOURCE = True

1849. By Christopher Lee

Revert addition of ModelClient2_1, just 2_0 instead.

Revision history for this message
Curtis Hovey (sinzui) wrote :

client_is_at_least_2_1() should be checking the client type like other scripts. see inline.

review: Needs Information (code)
1850. By Christopher Lee

Revert to multiple modelclients with better version check.

Revision history for this message
Curtis Hovey (sinzui) wrote :

thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'assess_model_migration.py'
2--- assess_model_migration.py 2017-01-20 20:30:41 +0000
3+++ assess_model_migration.py 2017-01-26 20:40:40 +0000
4@@ -18,6 +18,7 @@
5 BootstrapManager,
6 get_random_string
7 )
8+from jujupy.version_client import ModelClient2_0
9 from jujucharm import local_charm_path
10 from remote import remote_from_address
11 from utility import (
12@@ -43,26 +44,51 @@
13 bs2.client.enable_feature('migration')
14 bs2.client.env.juju_home = bs1.client.env.juju_home
15 with bs2.existing_booted_context(args.upload_tools):
16- source_client = bs1.client
17- dest_client = bs2.client
18- ensure_migration_including_resources_succeeds(
19- source_client, dest_client)
20+ source_client = bs2.client
21+ dest_client = bs1.client
22+ # Capture the migrated client so we can use it to assert it
23+ # continues to operate after the originating controller is torn
24+ # down.
25+ results = ensure_migration_with_resources_succeeds(
26+ source_client,
27+ dest_client)
28+ migrated_client, application, resource_contents = results
29+
30 ensure_model_logs_are_migrated(source_client, dest_client)
31- with temp_dir() as temp:
32- ensure_migrating_with_insufficient_user_permissions_fails(
33- source_client, dest_client, temp)
34- ensure_migrating_with_superuser_user_permissions_succeeds(
35- source_client, dest_client, temp)
36- # Tests that require features or bug fixes found in the
37- # 'develop' branch.
38- if args.use_develop:
39- ensure_superuser_can_migrate_other_user_models(
40- source_client, dest_client, temp)
41-
42+ assess_user_permission_model_migrations(source_client, dest_client)
43 if args.use_develop:
44- ensure_migration_rolls_back_on_failure(
45+ assess_development_branch_migrations(
46 source_client, dest_client)
47- ensure_api_login_redirects(source_client, dest_client)
48+
49+ if client_is_at_least_2_1(bs1.client):
50+ # Continue test where we ensure that a migrated model continues to
51+ # work after it's originating controller has been destroyed.
52+ assert_model_migrated_successfully(
53+ migrated_client, application, resource_contents)
54+ log.info(
55+ 'SUCCESS: Model operational after origin controller destroyed')
56+
57+
58+def assess_user_permission_model_migrations(source_client, dest_client):
59+ """Run migration tests for user permissions."""
60+ with temp_dir() as temp:
61+ ensure_migrating_with_insufficient_user_permissions_fails(
62+ source_client, dest_client, temp)
63+ ensure_migrating_with_superuser_user_permissions_succeeds(
64+ source_client, dest_client, temp)
65+
66+
67+def assess_development_branch_migrations(source_client, dest_client):
68+ with temp_dir() as temp:
69+ ensure_superuser_can_migrate_other_user_models(
70+ source_client, dest_client, temp)
71+ ensure_migration_rolls_back_on_failure(source_client, dest_client)
72+ ensure_api_login_redirects(source_client, dest_client)
73+
74+
75+def client_is_at_least_2_1(client):
76+ """Return true of the given ModelClient is version 2.1 or greater."""
77+ return not isinstance(client, ModelClient2_0)
78
79
80 def parse_args(argv):
81@@ -209,7 +235,7 @@
82 raise JujuAssertionError()
83
84
85-def ensure_migration_including_resources_succeeds(source_client, dest_client):
86+def ensure_migration_with_resources_succeeds(source_client, dest_client):
87 """Test simple migration of a model to another controller.
88
89 Ensure that migration a model that has an application, that uses resources,
90@@ -224,6 +250,9 @@
91 - Ensure it's operating as expected
92 - Add a new unit to the application to ensure the model is functional
93
94+ :return: Tuple containing migrated client object and the resource string
95+ that the charm deployed to it outputs.
96+
97 """
98 resource_contents = get_random_string()
99 test_model, application = deploy_simple_server_to_new_model(
100@@ -233,8 +262,8 @@
101 assert_model_migrated_successfully(
102 migration_target_client, application, resource_contents)
103
104- migration_target_client.remove_service(application)
105 log.info('SUCCESS: resources migrated')
106+ return migration_target_client, application, resource_contents
107
108
109 def assert_model_migrated_successfully(
110
111=== modified file 'jujupy/tests/test_version_client.py'
112--- jujupy/tests/test_version_client.py 2017-01-20 20:03:12 +0000
113+++ jujupy/tests/test_version_client.py 2017-01-26 20:40:40 +0000
114@@ -56,6 +56,7 @@
115 ModelClientRC,
116 IncompatibleConfigClass,
117 Juju1XBackend,
118+ ModelClient2_0,
119 ModelClient2_1,
120 VersionNotTestedError,
121 Status1X,
122@@ -130,6 +131,7 @@
123 yield '2.0-rc2'
124 yield '2.0-rc3'
125 yield '2.0-delta1'
126+ yield '2.1.0'
127 yield '2.2.0'
128
129 context = patch.object(
130@@ -182,7 +184,8 @@
131 test_fc('2.0-rc1', ModelClientRC)
132 test_fc('2.0-rc2', ModelClientRC)
133 test_fc('2.0-rc3', ModelClientRC)
134- test_fc('2.0-delta1', ModelClient2_1)
135+ test_fc('2.0-delta1', ModelClient2_0)
136+ test_fc('2.1.0', ModelClient2_1)
137 test_fc('2.2.0', ModelClient)
138 with self.assertRaises(StopIteration):
139 client_from_config('foo', None)
140
141=== modified file 'jujupy/version_client.py'
142--- jujupy/version_client.py 2017-01-20 20:03:12 +0000
143+++ jujupy/version_client.py 2017-01-26 20:40:40 +0000
144@@ -128,7 +128,14 @@
145 REGION_ENDPOINT_PROMPT = 'Enter the API endpoint url for the region:'
146
147
148-class ModelClientRC(ModelClient2_1):
149+class ModelClient2_0(ModelClient2_1):
150+ """Client for Juju 2.0"""
151+ # Outcome and output differs to 2.1 tests cannot assume 2.0 and 2.1 are
152+ # identical.
153+ pass
154+
155+
156+class ModelClientRC(ModelClient2_0):
157
158 def get_bootstrap_args(
159 self, upload_tools, config_filename, bootstrap_series=None,
160@@ -674,7 +681,9 @@
161 raise VersionNotTestedError(version)
162 elif re.match('^2\.0-rc[1-3]', version):
163 client_class = ModelClientRC
164- elif re.match('^2\.[0-1][.-]', version):
165+ elif re.match('^2\.0[.-]', version):
166+ client_class = ModelClient2_0
167+ elif re.match('^2\.1[.-]', version):
168 client_class = ModelClient2_1
169 else:
170 client_class = ModelClient
171
172=== modified file 'tests/test_assess_cloud.py'
173--- tests/test_assess_cloud.py 2017-01-25 19:02:32 +0000
174+++ tests/test_assess_cloud.py 2017-01-26 20:40:40 +0000
175@@ -21,7 +21,7 @@
176 fake_juju_client,
177 Juju2Backend,
178 )
179-from jujupy.version_client import ModelClient2_1
180+from jujupy.version_client import ModelClient2_0
181 from tests import (
182 FakeHomeTestCase,
183 observable_temp_file,
184@@ -170,7 +170,7 @@
185 client = client_from_args(args)
186 fcr_mock.assert_called_once_with('mycloud', None, {}, {},
187 self.juju_home)
188- self.assertIs(type(client), ModelClient2_1)
189+ self.assertIs(type(client), ModelClient2_0)
190 self.assertIs(type(client._backend), Juju2Backend)
191 self.assertEqual(client.version, '2.0.x')
192 self.assertIs(client.env, fcr_mock.return_value)
193
194=== modified file 'tests/test_assess_model_migration.py'
195--- tests/test_assess_model_migration.py 2017-01-20 20:30:41 +0000
196+++ tests/test_assess_model_migration.py 2017-01-26 20:40:40 +0000
197@@ -32,6 +32,11 @@
198 JujuData,
199 SoftDeadlineExceeded,
200 )
201+from jujupy.version_client import (
202+ ModelClient2_0,
203+ ModelClient2_1,
204+ ModelClientRC,
205+)
206 from tests import (
207 client_past_deadline,
208 FakeHomeTestCase,
209@@ -689,6 +694,24 @@
210 amm.raise_if_shared_machines([1, 1])
211
212
213+class TestClientIsAtLeast21(TestCase):
214+
215+ def test_returns_true_when_21(self):
216+ client = ModelClient2_1(JujuData('local', juju_home=''), None, None)
217+ self.assertTrue(amm.client_is_at_least_2_1(client))
218+
219+ def test_returns_true_when_greater_than_21(self):
220+ client = ModelClient(JujuData('local', juju_home=''), None, None)
221+ self.assertTrue(amm.client_is_at_least_2_1(client))
222+
223+ def test_returns_false_when_not_at_least_21(self):
224+ client = ModelClient2_0(JujuData('local', juju_home=''), None, None)
225+ self.assertFalse(amm.client_is_at_least_2_1(client))
226+
227+ client = ModelClientRC(JujuData('local', juju_home=''), None, None)
228+ self.assertFalse(amm.client_is_at_least_2_1(client))
229+
230+
231 class TestMain(TestCase):
232
233 def test_main(self):
234@@ -714,6 +737,50 @@
235 return patch.object(amm, target, autospec=True)
236
237
238+class TestAssessUserPermissionModelMigrations(TestCase):
239+
240+ def test_all_test_called(self):
241+ source_client = Mock()
242+ dest_client = Mock()
243+ patch_insuff = patch_amm(
244+ 'ensure_migrating_with_insufficient_user_permissions_fails')
245+ patch_super = patch_amm(
246+ 'ensure_migrating_with_superuser_user_permissions_succeeds')
247+ with patch.object(
248+ amm, 'temp_dir',
249+ autospec=True,
250+ return_value=tmp_ctx()):
251+ with patch_insuff as m_insuff:
252+ with patch_super as m_super:
253+ amm.assess_user_permission_model_migrations(
254+ source_client, dest_client)
255+ m_insuff.assert_called_once_with(
256+ source_client, dest_client, '/tmp/dir')
257+ m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
258+
259+
260+class TestAssessDevelopmentBranchMigrations(TestCase):
261+ def test_all_test_called(self):
262+ source_client = Mock()
263+ dest_client = Mock()
264+ patch_super = patch_amm(
265+ 'ensure_superuser_can_migrate_other_user_models')
266+ patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
267+ patch_api = patch_amm('ensure_api_login_redirects')
268+ with patch.object(
269+ amm, 'temp_dir',
270+ autospec=True,
271+ return_value=tmp_ctx()):
272+ with patch_super as m_super:
273+ with patch_rollback as m_rollback:
274+ with patch_api as m_api:
275+ amm.assess_development_branch_migrations(
276+ source_client, dest_client)
277+ m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
278+ m_rollback.assert_called_once_with(source_client, dest_client)
279+ m_api.assert_called_once_with(source_client, dest_client)
280+
281+
282 class TestAssessModelMigration(TestCase):
283
284 def test_runs_develop_tests_when_requested(self):
285@@ -725,40 +792,38 @@
286 bs1.booted_context.return_value = noop_context()
287 bs2.existing_booted_context.return_value = noop_context()
288
289+ patch_user_tests = patch_amm('assess_user_permission_model_migrations')
290+ patch_dev_tests = patch_amm('assess_development_branch_migrations')
291 patch_between = patch_amm(
292- 'ensure_migration_including_resources_succeeds')
293- patch_user = patch_amm(
294- 'ensure_migrating_with_insufficient_user_permissions_fails')
295- patch_super = patch_amm(
296- 'ensure_migrating_with_superuser_user_permissions_succeeds')
297+ 'ensure_migration_with_resources_succeeds')
298 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
299 patch_logs = patch_amm('ensure_model_logs_are_migrated')
300- patch_superother = patch_amm(
301- 'ensure_superuser_can_migrate_other_user_models')
302- patch_redirects = patch_amm('ensure_api_login_redirects')
303- patch_deploy_simple = patch_amm('deploy_simple_server_to_new_model')
304-
305- with patch_between as m_between:
306- with patch_user as m_user, patch_super as m_super:
307- with patch_rollback as m_rollback, patch_logs as m_logs:
308- with patch_redirects as m_redirects:
309- with patch_superother as m_superother:
310- with patch.object(
311- amm, 'temp_dir',
312- autospec=True,
313- return_value=tmp_ctx()):
314- with patch_deploy_simple:
315+ patch_assert_migrated = patch_amm('assert_model_migrated_successfully')
316+ patch_21_client = patch_amm('client_is_at_least_2_1')
317+
318+ mig_client = Mock()
319+ app = 'application'
320+ resource_string = 'resource'
321+
322+ with patch_user_tests as m_user:
323+ with patch_between as m_between:
324+ m_between.return_value = mig_client, app, resource_string
325+ with patch_rollback as m_rollback:
326+ with patch_logs as m_logs:
327+ with patch_assert_migrated as m_am:
328+ with patch_dev_tests as m_dev_tests:
329+ with patch_21_client as m_21:
330+ m_21.return_value = True
331 amm.assess_model_migration(bs1, bs2, args)
332- source_client = bs1.client
333- dest_client = bs2.client
334+ source_client = bs2.client
335+ dest_client = bs1.client
336+ m_user.assert_called_once_with(source_client, dest_client)
337 m_between.assert_called_once_with(source_client, dest_client)
338- m_user.assert_called_once_with(source_client, dest_client, '/tmp/dir')
339- m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
340- m_rollback.assert_called_once_with(source_client, dest_client)
341 m_logs.assert_called_once_with(source_client, dest_client)
342- m_superother.assert_called_once_with(
343- source_client, dest_client, '/tmp/dir')
344- m_redirects.assert_called_once_with(source_client, dest_client)
345+ m_am.assert_called_once_with(mig_client, app, resource_string)
346+ m_dev_tests.assert_called_once_with(source_client, dest_client)
347+
348+ self.assertEqual(m_rollback.call_count, 0)
349
350 def test_does_not_run_develop_tests_by_default(self):
351 argv = [
352@@ -769,29 +834,32 @@
353 bs1.booted_context.return_value = noop_context()
354 bs2.existing_booted_context.return_value = noop_context()
355
356- patch_user = patch_amm(
357- 'ensure_migrating_with_insufficient_user_permissions_fails')
358- patch_super = patch_amm(
359- 'ensure_migrating_with_superuser_user_permissions_succeeds')
360+ patch_user_tests = patch_amm('assess_user_permission_model_migrations')
361 patch_between = patch_amm(
362- 'ensure_migration_including_resources_succeeds')
363+ 'ensure_migration_with_resources_succeeds')
364 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
365 patch_logs = patch_amm('ensure_model_logs_are_migrated')
366-
367- with patch_user as m_user:
368- with patch_super as m_super:
369- with patch_between as m_between:
370- with patch_rollback as m_rollback:
371- with patch_logs as m_logs:
372- with patch.object(
373- amm, 'temp_dir',
374- autospec=True, return_value=tmp_ctx()):
375+ patch_assert_migrated = patch_amm('assert_model_migrated_successfully')
376+ patch_21_client = patch_amm('client_is_at_least_2_1')
377+
378+ mig_client = Mock()
379+ app = 'application'
380+ resource_string = 'resource'
381+
382+ with patch_user_tests as m_user:
383+ with patch_between as m_between:
384+ m_between.return_value = mig_client, app, resource_string
385+ with patch_rollback as m_rollback:
386+ with patch_logs as m_logs:
387+ with patch_assert_migrated as m_am:
388+ with patch_21_client as m_21:
389+ m_21.return_value = True
390 amm.assess_model_migration(bs1, bs2, args)
391- source_client = bs1.client
392- dest_client = bs2.client
393- m_user.assert_called_once_with(source_client, dest_client, '/tmp/dir')
394- m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
395+ source_client = bs2.client
396+ dest_client = bs1.client
397+ m_user.assert_called_once_with(source_client, dest_client)
398 m_between.assert_called_once_with(source_client, dest_client)
399 m_logs.assert_called_once_with(source_client, dest_client)
400+ m_am.assert_called_once_with(mig_client, app, resource_string)
401
402 self.assertEqual(m_rollback.call_count, 0)

Subscribers

People subscribed via source and target branches