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
=== modified file 'assess_model_migration.py'
--- assess_model_migration.py 2017-01-20 20:30:41 +0000
+++ assess_model_migration.py 2017-01-26 20:40:40 +0000
@@ -18,6 +18,7 @@
18 BootstrapManager,18 BootstrapManager,
19 get_random_string19 get_random_string
20 )20 )
21from jujupy.version_client import ModelClient2_0
21from jujucharm import local_charm_path22from jujucharm import local_charm_path
22from remote import remote_from_address23from remote import remote_from_address
23from utility import (24from utility import (
@@ -43,26 +44,51 @@
43 bs2.client.enable_feature('migration')44 bs2.client.enable_feature('migration')
44 bs2.client.env.juju_home = bs1.client.env.juju_home45 bs2.client.env.juju_home = bs1.client.env.juju_home
45 with bs2.existing_booted_context(args.upload_tools):46 with bs2.existing_booted_context(args.upload_tools):
46 source_client = bs1.client47 source_client = bs2.client
47 dest_client = bs2.client48 dest_client = bs1.client
48 ensure_migration_including_resources_succeeds(49 # Capture the migrated client so we can use it to assert it
49 source_client, dest_client)50 # continues to operate after the originating controller is torn
51 # down.
52 results = ensure_migration_with_resources_succeeds(
53 source_client,
54 dest_client)
55 migrated_client, application, resource_contents = results
56
50 ensure_model_logs_are_migrated(source_client, dest_client)57 ensure_model_logs_are_migrated(source_client, dest_client)
51 with temp_dir() as temp:58 assess_user_permission_model_migrations(source_client, dest_client)
52 ensure_migrating_with_insufficient_user_permissions_fails(
53 source_client, dest_client, temp)
54 ensure_migrating_with_superuser_user_permissions_succeeds(
55 source_client, dest_client, temp)
56 # Tests that require features or bug fixes found in the
57 # 'develop' branch.
58 if args.use_develop:
59 ensure_superuser_can_migrate_other_user_models(
60 source_client, dest_client, temp)
61
62 if args.use_develop:59 if args.use_develop:
63 ensure_migration_rolls_back_on_failure(60 assess_development_branch_migrations(
64 source_client, dest_client)61 source_client, dest_client)
65 ensure_api_login_redirects(source_client, dest_client)62
63 if client_is_at_least_2_1(bs1.client):
64 # Continue test where we ensure that a migrated model continues to
65 # work after it's originating controller has been destroyed.
66 assert_model_migrated_successfully(
67 migrated_client, application, resource_contents)
68 log.info(
69 'SUCCESS: Model operational after origin controller destroyed')
70
71
72def assess_user_permission_model_migrations(source_client, dest_client):
73 """Run migration tests for user permissions."""
74 with temp_dir() as temp:
75 ensure_migrating_with_insufficient_user_permissions_fails(
76 source_client, dest_client, temp)
77 ensure_migrating_with_superuser_user_permissions_succeeds(
78 source_client, dest_client, temp)
79
80
81def assess_development_branch_migrations(source_client, dest_client):
82 with temp_dir() as temp:
83 ensure_superuser_can_migrate_other_user_models(
84 source_client, dest_client, temp)
85 ensure_migration_rolls_back_on_failure(source_client, dest_client)
86 ensure_api_login_redirects(source_client, dest_client)
87
88
89def client_is_at_least_2_1(client):
90 """Return true of the given ModelClient is version 2.1 or greater."""
91 return not isinstance(client, ModelClient2_0)
6692
6793
68def parse_args(argv):94def parse_args(argv):
@@ -209,7 +235,7 @@
209 raise JujuAssertionError()235 raise JujuAssertionError()
210236
211237
212def ensure_migration_including_resources_succeeds(source_client, dest_client):238def ensure_migration_with_resources_succeeds(source_client, dest_client):
213 """Test simple migration of a model to another controller.239 """Test simple migration of a model to another controller.
214240
215 Ensure that migration a model that has an application, that uses resources,241 Ensure that migration a model that has an application, that uses resources,
@@ -224,6 +250,9 @@
224 - Ensure it's operating as expected250 - Ensure it's operating as expected
225 - Add a new unit to the application to ensure the model is functional251 - Add a new unit to the application to ensure the model is functional
226252
253 :return: Tuple containing migrated client object and the resource string
254 that the charm deployed to it outputs.
255
227 """256 """
228 resource_contents = get_random_string()257 resource_contents = get_random_string()
229 test_model, application = deploy_simple_server_to_new_model(258 test_model, application = deploy_simple_server_to_new_model(
@@ -233,8 +262,8 @@
233 assert_model_migrated_successfully(262 assert_model_migrated_successfully(
234 migration_target_client, application, resource_contents)263 migration_target_client, application, resource_contents)
235264
236 migration_target_client.remove_service(application)
237 log.info('SUCCESS: resources migrated')265 log.info('SUCCESS: resources migrated')
266 return migration_target_client, application, resource_contents
238267
239268
240def assert_model_migrated_successfully(269def assert_model_migrated_successfully(
241270
=== modified file 'jujupy/tests/test_version_client.py'
--- jujupy/tests/test_version_client.py 2017-01-20 20:03:12 +0000
+++ jujupy/tests/test_version_client.py 2017-01-26 20:40:40 +0000
@@ -56,6 +56,7 @@
56 ModelClientRC,56 ModelClientRC,
57 IncompatibleConfigClass,57 IncompatibleConfigClass,
58 Juju1XBackend,58 Juju1XBackend,
59 ModelClient2_0,
59 ModelClient2_1,60 ModelClient2_1,
60 VersionNotTestedError,61 VersionNotTestedError,
61 Status1X,62 Status1X,
@@ -130,6 +131,7 @@
130 yield '2.0-rc2'131 yield '2.0-rc2'
131 yield '2.0-rc3'132 yield '2.0-rc3'
132 yield '2.0-delta1'133 yield '2.0-delta1'
134 yield '2.1.0'
133 yield '2.2.0'135 yield '2.2.0'
134136
135 context = patch.object(137 context = patch.object(
@@ -182,7 +184,8 @@
182 test_fc('2.0-rc1', ModelClientRC)184 test_fc('2.0-rc1', ModelClientRC)
183 test_fc('2.0-rc2', ModelClientRC)185 test_fc('2.0-rc2', ModelClientRC)
184 test_fc('2.0-rc3', ModelClientRC)186 test_fc('2.0-rc3', ModelClientRC)
185 test_fc('2.0-delta1', ModelClient2_1)187 test_fc('2.0-delta1', ModelClient2_0)
188 test_fc('2.1.0', ModelClient2_1)
186 test_fc('2.2.0', ModelClient)189 test_fc('2.2.0', ModelClient)
187 with self.assertRaises(StopIteration):190 with self.assertRaises(StopIteration):
188 client_from_config('foo', None)191 client_from_config('foo', None)
189192
=== modified file 'jujupy/version_client.py'
--- jujupy/version_client.py 2017-01-20 20:03:12 +0000
+++ jujupy/version_client.py 2017-01-26 20:40:40 +0000
@@ -128,7 +128,14 @@
128 REGION_ENDPOINT_PROMPT = 'Enter the API endpoint url for the region:'128 REGION_ENDPOINT_PROMPT = 'Enter the API endpoint url for the region:'
129129
130130
131class ModelClientRC(ModelClient2_1):131class ModelClient2_0(ModelClient2_1):
132 """Client for Juju 2.0"""
133 # Outcome and output differs to 2.1 tests cannot assume 2.0 and 2.1 are
134 # identical.
135 pass
136
137
138class ModelClientRC(ModelClient2_0):
132139
133 def get_bootstrap_args(140 def get_bootstrap_args(
134 self, upload_tools, config_filename, bootstrap_series=None,141 self, upload_tools, config_filename, bootstrap_series=None,
@@ -674,7 +681,9 @@
674 raise VersionNotTestedError(version)681 raise VersionNotTestedError(version)
675 elif re.match('^2\.0-rc[1-3]', version):682 elif re.match('^2\.0-rc[1-3]', version):
676 client_class = ModelClientRC683 client_class = ModelClientRC
677 elif re.match('^2\.[0-1][.-]', version):684 elif re.match('^2\.0[.-]', version):
685 client_class = ModelClient2_0
686 elif re.match('^2\.1[.-]', version):
678 client_class = ModelClient2_1687 client_class = ModelClient2_1
679 else:688 else:
680 client_class = ModelClient689 client_class = ModelClient
681690
=== modified file 'tests/test_assess_cloud.py'
--- tests/test_assess_cloud.py 2017-01-25 19:02:32 +0000
+++ tests/test_assess_cloud.py 2017-01-26 20:40:40 +0000
@@ -21,7 +21,7 @@
21 fake_juju_client,21 fake_juju_client,
22 Juju2Backend,22 Juju2Backend,
23 )23 )
24from jujupy.version_client import ModelClient2_124from jujupy.version_client import ModelClient2_0
25from tests import (25from tests import (
26 FakeHomeTestCase,26 FakeHomeTestCase,
27 observable_temp_file,27 observable_temp_file,
@@ -170,7 +170,7 @@
170 client = client_from_args(args)170 client = client_from_args(args)
171 fcr_mock.assert_called_once_with('mycloud', None, {}, {},171 fcr_mock.assert_called_once_with('mycloud', None, {}, {},
172 self.juju_home)172 self.juju_home)
173 self.assertIs(type(client), ModelClient2_1)173 self.assertIs(type(client), ModelClient2_0)
174 self.assertIs(type(client._backend), Juju2Backend)174 self.assertIs(type(client._backend), Juju2Backend)
175 self.assertEqual(client.version, '2.0.x')175 self.assertEqual(client.version, '2.0.x')
176 self.assertIs(client.env, fcr_mock.return_value)176 self.assertIs(client.env, fcr_mock.return_value)
177177
=== modified file 'tests/test_assess_model_migration.py'
--- tests/test_assess_model_migration.py 2017-01-20 20:30:41 +0000
+++ tests/test_assess_model_migration.py 2017-01-26 20:40:40 +0000
@@ -32,6 +32,11 @@
32 JujuData,32 JujuData,
33 SoftDeadlineExceeded,33 SoftDeadlineExceeded,
34 )34 )
35from jujupy.version_client import (
36 ModelClient2_0,
37 ModelClient2_1,
38 ModelClientRC,
39)
35from tests import (40from tests import (
36 client_past_deadline,41 client_past_deadline,
37 FakeHomeTestCase,42 FakeHomeTestCase,
@@ -689,6 +694,24 @@
689 amm.raise_if_shared_machines([1, 1])694 amm.raise_if_shared_machines([1, 1])
690695
691696
697class TestClientIsAtLeast21(TestCase):
698
699 def test_returns_true_when_21(self):
700 client = ModelClient2_1(JujuData('local', juju_home=''), None, None)
701 self.assertTrue(amm.client_is_at_least_2_1(client))
702
703 def test_returns_true_when_greater_than_21(self):
704 client = ModelClient(JujuData('local', juju_home=''), None, None)
705 self.assertTrue(amm.client_is_at_least_2_1(client))
706
707 def test_returns_false_when_not_at_least_21(self):
708 client = ModelClient2_0(JujuData('local', juju_home=''), None, None)
709 self.assertFalse(amm.client_is_at_least_2_1(client))
710
711 client = ModelClientRC(JujuData('local', juju_home=''), None, None)
712 self.assertFalse(amm.client_is_at_least_2_1(client))
713
714
692class TestMain(TestCase):715class TestMain(TestCase):
693716
694 def test_main(self):717 def test_main(self):
@@ -714,6 +737,50 @@
714 return patch.object(amm, target, autospec=True)737 return patch.object(amm, target, autospec=True)
715738
716739
740class TestAssessUserPermissionModelMigrations(TestCase):
741
742 def test_all_test_called(self):
743 source_client = Mock()
744 dest_client = Mock()
745 patch_insuff = patch_amm(
746 'ensure_migrating_with_insufficient_user_permissions_fails')
747 patch_super = patch_amm(
748 'ensure_migrating_with_superuser_user_permissions_succeeds')
749 with patch.object(
750 amm, 'temp_dir',
751 autospec=True,
752 return_value=tmp_ctx()):
753 with patch_insuff as m_insuff:
754 with patch_super as m_super:
755 amm.assess_user_permission_model_migrations(
756 source_client, dest_client)
757 m_insuff.assert_called_once_with(
758 source_client, dest_client, '/tmp/dir')
759 m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
760
761
762class TestAssessDevelopmentBranchMigrations(TestCase):
763 def test_all_test_called(self):
764 source_client = Mock()
765 dest_client = Mock()
766 patch_super = patch_amm(
767 'ensure_superuser_can_migrate_other_user_models')
768 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
769 patch_api = patch_amm('ensure_api_login_redirects')
770 with patch.object(
771 amm, 'temp_dir',
772 autospec=True,
773 return_value=tmp_ctx()):
774 with patch_super as m_super:
775 with patch_rollback as m_rollback:
776 with patch_api as m_api:
777 amm.assess_development_branch_migrations(
778 source_client, dest_client)
779 m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
780 m_rollback.assert_called_once_with(source_client, dest_client)
781 m_api.assert_called_once_with(source_client, dest_client)
782
783
717class TestAssessModelMigration(TestCase):784class TestAssessModelMigration(TestCase):
718785
719 def test_runs_develop_tests_when_requested(self):786 def test_runs_develop_tests_when_requested(self):
@@ -725,40 +792,38 @@
725 bs1.booted_context.return_value = noop_context()792 bs1.booted_context.return_value = noop_context()
726 bs2.existing_booted_context.return_value = noop_context()793 bs2.existing_booted_context.return_value = noop_context()
727794
795 patch_user_tests = patch_amm('assess_user_permission_model_migrations')
796 patch_dev_tests = patch_amm('assess_development_branch_migrations')
728 patch_between = patch_amm(797 patch_between = patch_amm(
729 'ensure_migration_including_resources_succeeds')798 'ensure_migration_with_resources_succeeds')
730 patch_user = patch_amm(
731 'ensure_migrating_with_insufficient_user_permissions_fails')
732 patch_super = patch_amm(
733 'ensure_migrating_with_superuser_user_permissions_succeeds')
734 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')799 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
735 patch_logs = patch_amm('ensure_model_logs_are_migrated')800 patch_logs = patch_amm('ensure_model_logs_are_migrated')
736 patch_superother = patch_amm(801 patch_assert_migrated = patch_amm('assert_model_migrated_successfully')
737 'ensure_superuser_can_migrate_other_user_models')802 patch_21_client = patch_amm('client_is_at_least_2_1')
738 patch_redirects = patch_amm('ensure_api_login_redirects')803
739 patch_deploy_simple = patch_amm('deploy_simple_server_to_new_model')804 mig_client = Mock()
740805 app = 'application'
741 with patch_between as m_between:806 resource_string = 'resource'
742 with patch_user as m_user, patch_super as m_super:807
743 with patch_rollback as m_rollback, patch_logs as m_logs:808 with patch_user_tests as m_user:
744 with patch_redirects as m_redirects:809 with patch_between as m_between:
745 with patch_superother as m_superother:810 m_between.return_value = mig_client, app, resource_string
746 with patch.object(811 with patch_rollback as m_rollback:
747 amm, 'temp_dir',812 with patch_logs as m_logs:
748 autospec=True,813 with patch_assert_migrated as m_am:
749 return_value=tmp_ctx()):814 with patch_dev_tests as m_dev_tests:
750 with patch_deploy_simple:815 with patch_21_client as m_21:
816 m_21.return_value = True
751 amm.assess_model_migration(bs1, bs2, args)817 amm.assess_model_migration(bs1, bs2, args)
752 source_client = bs1.client818 source_client = bs2.client
753 dest_client = bs2.client819 dest_client = bs1.client
820 m_user.assert_called_once_with(source_client, dest_client)
754 m_between.assert_called_once_with(source_client, dest_client)821 m_between.assert_called_once_with(source_client, dest_client)
755 m_user.assert_called_once_with(source_client, dest_client, '/tmp/dir')
756 m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
757 m_rollback.assert_called_once_with(source_client, dest_client)
758 m_logs.assert_called_once_with(source_client, dest_client)822 m_logs.assert_called_once_with(source_client, dest_client)
759 m_superother.assert_called_once_with(823 m_am.assert_called_once_with(mig_client, app, resource_string)
760 source_client, dest_client, '/tmp/dir')824 m_dev_tests.assert_called_once_with(source_client, dest_client)
761 m_redirects.assert_called_once_with(source_client, dest_client)825
826 self.assertEqual(m_rollback.call_count, 0)
762827
763 def test_does_not_run_develop_tests_by_default(self):828 def test_does_not_run_develop_tests_by_default(self):
764 argv = [829 argv = [
@@ -769,29 +834,32 @@
769 bs1.booted_context.return_value = noop_context()834 bs1.booted_context.return_value = noop_context()
770 bs2.existing_booted_context.return_value = noop_context()835 bs2.existing_booted_context.return_value = noop_context()
771836
772 patch_user = patch_amm(837 patch_user_tests = patch_amm('assess_user_permission_model_migrations')
773 'ensure_migrating_with_insufficient_user_permissions_fails')
774 patch_super = patch_amm(
775 'ensure_migrating_with_superuser_user_permissions_succeeds')
776 patch_between = patch_amm(838 patch_between = patch_amm(
777 'ensure_migration_including_resources_succeeds')839 'ensure_migration_with_resources_succeeds')
778 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')840 patch_rollback = patch_amm('ensure_migration_rolls_back_on_failure')
779 patch_logs = patch_amm('ensure_model_logs_are_migrated')841 patch_logs = patch_amm('ensure_model_logs_are_migrated')
780842 patch_assert_migrated = patch_amm('assert_model_migrated_successfully')
781 with patch_user as m_user:843 patch_21_client = patch_amm('client_is_at_least_2_1')
782 with patch_super as m_super:844
783 with patch_between as m_between:845 mig_client = Mock()
784 with patch_rollback as m_rollback:846 app = 'application'
785 with patch_logs as m_logs:847 resource_string = 'resource'
786 with patch.object(848
787 amm, 'temp_dir',849 with patch_user_tests as m_user:
788 autospec=True, return_value=tmp_ctx()):850 with patch_between as m_between:
851 m_between.return_value = mig_client, app, resource_string
852 with patch_rollback as m_rollback:
853 with patch_logs as m_logs:
854 with patch_assert_migrated as m_am:
855 with patch_21_client as m_21:
856 m_21.return_value = True
789 amm.assess_model_migration(bs1, bs2, args)857 amm.assess_model_migration(bs1, bs2, args)
790 source_client = bs1.client858 source_client = bs2.client
791 dest_client = bs2.client859 dest_client = bs1.client
792 m_user.assert_called_once_with(source_client, dest_client, '/tmp/dir')860 m_user.assert_called_once_with(source_client, dest_client)
793 m_super.assert_called_once_with(source_client, dest_client, '/tmp/dir')
794 m_between.assert_called_once_with(source_client, dest_client)861 m_between.assert_called_once_with(source_client, dest_client)
795 m_logs.assert_called_once_with(source_client, dest_client)862 m_logs.assert_called_once_with(source_client, dest_client)
863 m_am.assert_called_once_with(mig_client, app, resource_string)
796864
797 self.assertEqual(m_rollback.call_count, 0)865 self.assertEqual(m_rollback.call_count, 0)

Subscribers

People subscribed via source and target branches