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

Proposed by Christopher Lee
Status: Needs review
Proposed branch: lp:~veebers/juju-ci-tools/adding_model_destory_test
Merge into: lp:juju-ci-tools
Diff against target: 266 lines (+257/-0)
2 files modified
assess_destroy_model.py (+122/-0)
tests/test_assess_destroy_model.py (+135/-0)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/adding_model_destory_test
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+303215@code.launchpad.net

Commit message

Description of the change

Add functional test for bug: https://bugs.launchpad.net/juju-core/+bug/1582021
This test was request by core devs in case of regression.

It does a couple of things against the grain of normal usage of jujupy, namely using the 'switch' command. This is due to the nature of the bug and test.

It also has a gross workaround where it uses a sleep around the creation and deletion of a model due to bug https://bugs.launchpad.net/juju-core/+bug/1613960. Perhaps there is a better way to do this?

Tests that deleting the in-focus model removes the current model and the ability to switch to a new model to set it.

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

Thank you.

review: Approve (code)

Unmerged revisions

1561. By Christopher Lee

Actually use unit tests and with a fix.

1560. By Christopher Lee

Add gross workaround for bug (1613960)

1559. By Christopher Lee

Add reference to bug this test is to cover

1558. By Christopher Lee

Fix status command check

1557. By Christopher Lee

Fixed call to 'switch'.

1556. By Christopher Lee

Initial pass of simple test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'assess_destroy_model.py'
2--- assess_destroy_model.py 1970-01-01 00:00:00 +0000
3+++ assess_destroy_model.py 2016-08-19 00:10:42 +0000
4@@ -0,0 +1,122 @@
5+#!/usr/bin/env python
6+"""Test destroy model functionality.
7+
8+Ensures that juju is able to destroy a hosted model and that systems run as
9+expected during and after a model destroy.
10+
11+"""
12+
13+from __future__ import print_function
14+
15+import argparse
16+import logging
17+import subprocess
18+import sys
19+import time
20+
21+from deploy_stack import (
22+ BootstrapManager,
23+)
24+from utility import (
25+ JujuAssertionError,
26+ add_basic_testing_arguments,
27+ configure_logging,
28+)
29+
30+
31+__metaclass__ = type
32+
33+
34+log = logging.getLogger("assess_destroy_model")
35+
36+
37+def assess_destroy_model(client):
38+ """Ensure current model is stored when expected.
39+
40+ When deleting a model it must be unset (and thus will cause an error if
41+ status without '-m' is used).
42+ When unset list-models must still work.
43+ 'switch-ing' to another model must set current model.
44+
45+ Covers this bug: https://bugs.launchpad.net/juju-core/+bug/1582021
46+
47+ """
48+ ensure_only_expected_models_exist(
49+ client, ['controller', client.env.environment])
50+
51+ new_model = client.add_model(client.env.clone('new-model'))
52+
53+ ensure_only_expected_models_exist(
54+ client,
55+ ['controller', client.env.environment, new_model.env.environment])
56+
57+ # Creating and deleting a model quickly can leave that model in list-models
58+ # output.
59+ # as per: https://bugs.launchpad.net/juju-core/+bug/1613960
60+ time.sleep(10)
61+ new_model.destroy_model()
62+ time.sleep(10)
63+
64+ ensure_only_expected_models_exist(
65+ client, ['controller', client.env.environment])
66+
67+ ensure_no_current_model(client)
68+
69+ ensure_switching_models_sets_current_model(client)
70+
71+
72+def ensure_switching_models_sets_current_model(client):
73+ full_model_name = '{}:{}'.format(
74+ client.env.controller.name, client.env.environment)
75+ client.juju('switch', (full_model_name), include_e=False)
76+ try:
77+ client.juju('status', (), include_e=False)
78+ except subprocess.CalledProcessError as e:
79+ raise JujuAssertionError('Call to status failed: {}'.format(str(e)))
80+
81+
82+def ensure_only_expected_models_exist(client, model_names):
83+ models = client.get_models()
84+ existing_models = [m['name'] for m in models['models']]
85+
86+ if set(existing_models).difference(set(model_names)) != set():
87+ raise JujuAssertionError(
88+ 'Unexpected models encountered.\n'
89+ 'Expected: {}\nGot: {}'.format(model_names, existing_models))
90+
91+
92+def ensure_no_current_model(client):
93+ """Raise exception if there is any current model set anywhere."""
94+ if '*' in client.get_juju_output('list-models', include_e=False):
95+ raise JujuAssertionError('list-models shows current model set.')
96+
97+ current_model = client.get_models().get('current-model', None)
98+ if current_model is not None:
99+ raise JujuAssertionError(
100+ 'Current model is still set: {}'.format(current_model))
101+
102+ try:
103+ client.juju('status', (), include_e=False, check=True)
104+ except subprocess.CalledProcessError:
105+ log.info('Status call failed as expected.')
106+ return
107+ raise JujuAssertionError('Status call did not fail as expected.')
108+
109+
110+def parse_args(argv):
111+ parser = argparse.ArgumentParser(description="Test Destroy Model.")
112+ add_basic_testing_arguments(parser)
113+ return parser.parse_args(argv)
114+
115+
116+def main(argv=None):
117+ args = parse_args(argv)
118+ configure_logging(args.verbose)
119+ bs_manager = BootstrapManager.from_args(args)
120+ with bs_manager.booted_context(args.upload_tools):
121+ assess_destroy_model(bs_manager.client)
122+ return 0
123+
124+
125+if __name__ == '__main__':
126+ sys.exit(main())
127
128=== added file 'tests/test_assess_destroy_model.py'
129--- tests/test_assess_destroy_model.py 1970-01-01 00:00:00 +0000
130+++ tests/test_assess_destroy_model.py 2016-08-19 00:10:42 +0000
131@@ -0,0 +1,135 @@
132+"""Tests for assess_destroy_model module."""
133+
134+import argparse
135+from mock import Mock, patch
136+import StringIO
137+import subprocess
138+
139+import assess_destroy_model as adm
140+from tests import (
141+ parse_error,
142+ TestCase,
143+)
144+from utility import JujuAssertionError
145+
146+
147+class TestParseArgs(TestCase):
148+
149+ def test_default_args(self):
150+ args = adm.parse_args(
151+ ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod"])
152+ self.assertEqual(
153+ args,
154+ argparse.Namespace(
155+ env="an-env",
156+ juju_bin='/bin/juju',
157+ logs='/tmp/logs',
158+ temp_env_name='an-env-mod',
159+ debug=False,
160+ agent_stream=None,
161+ agent_url=None,
162+ bootstrap_host=None,
163+ keep_env=False,
164+ machine=[],
165+ region=None,
166+ series=None,
167+ upload_tools=False,
168+ verbose=20))
169+
170+ def test_help(self):
171+ fake_stdout = StringIO.StringIO()
172+ with parse_error(self) as fake_stderr:
173+ with patch("sys.stdout", fake_stdout):
174+ adm.parse_args(["--help"])
175+ self.assertEqual("", fake_stderr.getvalue())
176+ self.assertIn("Test Destroy Model.", fake_stdout.getvalue())
177+
178+
179+class TestEnsureSwithingModelsSetsCurrentModel(TestCase):
180+
181+ def test_raises_exception_on_status_failure(self):
182+ client = Mock()
183+ client.juju.side_effect = [
184+ None,
185+ subprocess.CalledProcessError(-1, None, None),
186+ ]
187+ with self.assertRaises(JujuAssertionError):
188+ adm.ensure_switching_models_sets_current_model(client)
189+
190+ def test_no_exceptions_raised_on_successful_switch(self):
191+ client = Mock()
192+ adm.ensure_switching_models_sets_current_model(client)
193+
194+
195+class TestEnsureNoCurrentModel(TestCase):
196+
197+ def test_raises_on_current_set_in_listmodels(self):
198+ client = Mock()
199+ client.get_juju_output.return_value = '*some-model'
200+ client.get_models.return_value = {}
201+
202+ with self.assertRaises(JujuAssertionError) as ex:
203+ adm.ensure_no_current_model(client)
204+ exception_message = str(ex.exception)
205+ self.assertEqual(
206+ exception_message, 'list-models shows current model set.')
207+
208+ def test_raises_on_current_model_in_models_list(self):
209+ client = Mock()
210+ client.get_juju_output.return_value = ''
211+ client.get_models.return_value = {'current-model': 'foo'}
212+
213+ with self.assertRaises(JujuAssertionError) as ex:
214+ adm.ensure_no_current_model(client)
215+ exception_message = str(ex.exception)
216+ self.assertEqual(
217+ exception_message, 'Current model is still set: foo')
218+
219+ def test_raises_exception_if_unexpectedly_passes(self):
220+ client = Mock()
221+ client.get_juju_output.return_value = ''
222+ client.get_models.return_value = {}
223+
224+ with self.assertRaises(JujuAssertionError) as ex:
225+ adm.ensure_no_current_model(client)
226+ exception_message = str(ex.exception)
227+ self.assertEqual(
228+ exception_message, 'Status call did not fail as expected.')
229+
230+ def test_passes_when_status_fails(self):
231+ client = Mock()
232+ client.get_juju_output.return_value = ''
233+ client.get_models.return_value = {}
234+ client.juju.side_effect = subprocess.CalledProcessError(-1, None, None)
235+
236+ adm.ensure_no_current_model(client)
237+
238+
239+class TestEnsureOnlyExpectedModelsExist(TestCase):
240+
241+ def test_not_raised_on_all_expected(self):
242+ to_check = ['a', 'b', 'c']
243+ actual_models = {
244+ 'models': [
245+ {'name': 'a'},
246+ {'name': 'b'},
247+ {'name': 'c'}
248+ ]}
249+
250+ mock_client = Mock()
251+ mock_client.get_models.return_value = actual_models
252+ adm.ensure_only_expected_models_exist(mock_client, to_check)
253+
254+ def test_raises_on_more_than_expected(self):
255+ to_check = ['a', 'b']
256+ actual_models = {
257+ 'models': [
258+ {'name': 'a'},
259+ {'name': 'b'},
260+ {'name': 'c'}
261+ ]}
262+
263+ mock_client = Mock()
264+ mock_client.get_models.return_value = actual_models
265+ with self.assertRaises(JujuAssertionError):
266+ adm.ensure_only_expected_models_exist(mock_client, to_check)

Subscribers

People subscribed via source and target branches