Merge lp:~veebers/juju-ci-tools/model_migration_controller_cleanup into lp:juju-ci-tools
- model_migration_controller_cleanup
- Merge into trunk
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 |
Related bugs: |
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.
- 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.
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?
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_
- 1849. By Christopher Lee
-
Revert addition of ModelClient2_1, just 2_0 instead.
Curtis Hovey (sinzui) wrote : | # |
client_
- 1850. By Christopher Lee
-
Revert to multiple modelclients with better version check.
Preview Diff
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) |
Thank you. I have a suggestion inline.