Merge lp:~gz/juju-ci-tools/common_container_networking into lp:juju-ci-tools
- common_container_networking
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1226 |
Proposed branch: | lp:~gz/juju-ci-tools/common_container_networking |
Merge into: | lp:juju-ci-tools |
Diff against target: |
335 lines (+148/-93) 2 files modified
assess_container_networking.py (+48/-78) tests/test_assess_container_networking.py (+100/-15) |
To merge this branch: | bzr merge lp:~gz/juju-ci-tools/common_container_networking |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+282640@code.launchpad.net |
Commit message
Description of the change
Make assess_
Switches to use BootstrapManager but retains --clean-environment as a special case of --keep-env. Pulls some of the parts of BootstrapManager out into the local test file for now, still saves a bunch of code.
Aaron Bentley (abentley) wrote : | # |
Okay, I think I get this better now. Because you're not hitting bootstrap_context, you're not writing client.env to disk-- you're just ignoring it. Its only value is the name, so you're using update_env to change the name.
I think this is a borderline case-- it might be better to load the proper client.env from disk, but this works. update_env is overkill, but it respects DRY.
Martin Packman (gz) wrote : | # |
Right, I think what I really want is an alternative first step method on BootstrapManager that loads the tempenv rather than does the bootstrap.
Preview Diff
1 | === modified file 'assess_container_networking.py' |
2 | --- assess_container_networking.py 2015-12-12 00:40:50 +0000 |
3 | +++ assess_container_networking.py 2016-01-14 17:48:44 +0000 |
4 | @@ -9,28 +9,24 @@ |
5 | import re |
6 | import os |
7 | import subprocess |
8 | +import sys |
9 | import tempfile |
10 | from textwrap import dedent |
11 | import time |
12 | |
13 | from deploy_stack import ( |
14 | + BootstrapManager, |
15 | + get_random_string, |
16 | update_env, |
17 | - dump_env_logs, |
18 | - get_random_string, |
19 | -) |
20 | -from jujuconfig import ( |
21 | - get_juju_home, |
22 | ) |
23 | from jujupy import ( |
24 | - parse_new_state_server_from_error, |
25 | - temp_bootstrap_env, |
26 | SimpleEnvironment, |
27 | EnvJujuClient, |
28 | ) |
29 | from utility import ( |
30 | + add_basic_testing_arguments, |
31 | + configure_logging, |
32 | wait_for_port, |
33 | - print_now, |
34 | - add_basic_testing_arguments, |
35 | ) |
36 | |
37 | |
38 | @@ -40,6 +36,9 @@ |
39 | __metaclass__ = type |
40 | |
41 | |
42 | +log = logging.getLogger("assess_container_networking") |
43 | + |
44 | + |
45 | def parse_args(argv=None): |
46 | """Parse all arguments.""" |
47 | |
48 | @@ -69,7 +68,11 @@ |
49 | |
50 | At termination, clean out services and machines from the environment |
51 | rather than destroying it.""")) |
52 | - return parser.parse_args(argv) |
53 | + args = parser.parse_args(argv) |
54 | + # Passing --clean-environment implies --keep-env |
55 | + if args.clean_environment: |
56 | + args.keep_env = True |
57 | + return args |
58 | |
59 | |
60 | def ssh(client, machine, cmd): |
61 | @@ -107,9 +110,14 @@ |
62 | # A short timeout is used for get_status here because if we don't get a |
63 | # response from get_status quickly then the environment almost |
64 | # certainly doesn't exist or needs recreating. |
65 | - status = client.get_status(5) |
66 | + try: |
67 | + status = client.get_status(5) |
68 | + except Exception as e: |
69 | + # TODO(gz): get_status should return a more specific error type. |
70 | + log.info("Could not clean existing env: %s", e) |
71 | + return False |
72 | |
73 | - for service in status.status['services']: |
74 | + for service in status.status.get('services', {}): |
75 | client.juju('remove-service', service) |
76 | |
77 | if not services_only: |
78 | @@ -214,6 +222,7 @@ |
79 | :param targets: machine IDs of machines to test |
80 | :return: None; |
81 | """ |
82 | + log.info('Waiting for the bootstrap machine agent to start.') |
83 | status = client.wait_for_started().status |
84 | source = targets[0] |
85 | dests = targets[1:] |
86 | @@ -336,15 +345,15 @@ |
87 | _assessment_iteration(client, test_containers) |
88 | |
89 | |
90 | -def assess_container_networking(client, args): |
91 | +def assess_container_networking(client, machine_type): |
92 | """Runs _assess_address_allocation, reboots hosts, repeat. |
93 | :param client: Juju client |
94 | - :param args: Parsed command line arguments |
95 | + :param machine_type: Container types to test |
96 | :return: None |
97 | """ |
98 | # Only test the containers we were asked to test |
99 | - if args.machine_type: |
100 | - types = [args.machine_type] |
101 | + if machine_type: |
102 | + types = [machine_type] |
103 | else: |
104 | types = [KVM_MACHINE, LXC_MACHINE] |
105 | |
106 | @@ -377,69 +386,30 @@ |
107 | _assess_container_networking(client, types, hosts, containers) |
108 | |
109 | |
110 | -def get_client(args): |
111 | - client = EnvJujuClient.by_version( |
112 | - SimpleEnvironment.from_config(args.env), |
113 | - args.juju_bin, args.debug |
114 | - ) |
115 | +def main(argv=None): |
116 | + args = parse_args(argv) |
117 | + configure_logging(args.verbose) |
118 | + bs_manager = BootstrapManager.from_args(args) |
119 | + client = bs_manager.client |
120 | client.enable_container_address_allocation() |
121 | - update_env(client.env, args.temp_env_name, |
122 | - series=args.series, bootstrap_host=args.bootstrap_host, |
123 | - agent_url=args.agent_url, agent_stream=args.agent_stream, |
124 | - region=args.region) |
125 | - return client |
126 | - |
127 | - |
128 | -def main(): |
129 | - args = parse_args() |
130 | - client = get_client(args) |
131 | - juju_home = get_juju_home() |
132 | - bootstrap_host = None |
133 | - success = True |
134 | - try: |
135 | - if args.clean_environment: |
136 | - try: |
137 | - if not clean_environment(client): |
138 | - with temp_bootstrap_env(juju_home, client): |
139 | - client.bootstrap(args.upload_tools) |
140 | - except Exception as e: |
141 | - logging.exception(e) |
142 | - client.destroy_environment() |
143 | - client = get_client(args) |
144 | - with temp_bootstrap_env(juju_home, client): |
145 | - client.bootstrap(args.upload_tools) |
146 | - else: |
147 | - client.destroy_environment() |
148 | - client = get_client(args) |
149 | - with temp_bootstrap_env(juju_home, client): |
150 | + # TODO(gz): Having to manipulate client env state here to get the temp env |
151 | + # is ugly, would ideally be captured in an explicit scope. |
152 | + update_env(client.env, bs_manager.temp_env_name, series=bs_manager.series, |
153 | + agent_url=bs_manager.agent_url, |
154 | + agent_stream=bs_manager.agent_stream, region=bs_manager.region) |
155 | + with bs_manager.top_context() as machines: |
156 | + bootstrap_required = True |
157 | + if args.clean_environment and clean_environment(client): |
158 | + bootstrap_required = False |
159 | + if bootstrap_required: |
160 | + with bs_manager.bootstrap_context(machines): |
161 | client.bootstrap(args.upload_tools) |
162 | - |
163 | - logging.info('Waiting for the bootstrap machine agent to start.') |
164 | - status = client.wait_for_started() |
165 | - mid, data = list(status.iter_machines())[0] |
166 | - bootstrap_host = data['dns-name'] |
167 | - |
168 | - assess_container_networking(client, args) |
169 | - |
170 | - except Exception as e: |
171 | - success = False |
172 | - logging.exception(e) |
173 | - try: |
174 | - if bootstrap_host is None: |
175 | - bootstrap_host = parse_new_state_server_from_error(e) |
176 | - except Exception as e: |
177 | - print_now('exception while dumping logs:\n') |
178 | - logging.exception(e) |
179 | - finally: |
180 | - if bootstrap_host is not None: |
181 | - dump_env_logs(client, bootstrap_host, args.logs) |
182 | - |
183 | - if args.clean_environment: |
184 | - clean_environment(client) |
185 | - else: |
186 | - client.destroy_environment() |
187 | - if not success: |
188 | - exit(1) |
189 | + with bs_manager.runtime_context(machines): |
190 | + assess_container_networking(client, args.machine_type) |
191 | + if args.clean_environment and not clean_environment(client): |
192 | + return 1 |
193 | + return 0 |
194 | + |
195 | |
196 | if __name__ == '__main__': |
197 | - main() |
198 | + sys.exit(main()) |
199 | |
200 | === modified file 'tests/test_assess_container_networking.py' |
201 | --- tests/test_assess_container_networking.py 2015-12-14 16:23:40 +0000 |
202 | +++ tests/test_assess_container_networking.py 2016-01-14 17:48:44 +0000 |
203 | @@ -1,6 +1,7 @@ |
204 | from argparse import Namespace |
205 | from copy import deepcopy |
206 | from contextlib import contextmanager |
207 | +import logging |
208 | |
209 | from mock import ( |
210 | patch, |
211 | @@ -16,6 +17,7 @@ |
212 | |
213 | import assess_container_networking as jcnet |
214 | from tests import ( |
215 | + FakeHomeTestCase, |
216 | parse_error, |
217 | TestCase, |
218 | ) |
219 | @@ -389,18 +391,101 @@ |
220 | ValueError, "Default route not found", |
221 | jcnet.assess_internet_connection, self.client, targets) |
222 | |
223 | - def test_get_client(self): |
224 | - args = Namespace( |
225 | - env="e", juju_bin="jb", debug=False, |
226 | - agent_stream="http://tools.testing/agents", temp_env_name="te", |
227 | - series="s", bootstrap_host="bh", agent_url="au", region="r") |
228 | - |
229 | - upenv = MagicMock() |
230 | - with patch.object(EnvJujuClient, "by_version"), \ |
231 | - patch.object(SimpleEnvironment, "from_config"), \ |
232 | - patch("assess_container_networking.update_env", upenv): |
233 | - |
234 | - jcnet.get_client(args) |
235 | - self.assertEqual(upenv.call_args[0][1], args.temp_env_name) |
236 | - for key, value in upenv.call_args[1].iteritems(): |
237 | - self.assertEqual(vars(args)[key], value) |
238 | + |
239 | +class TestMain(FakeHomeTestCase): |
240 | + |
241 | + @contextmanager |
242 | + def patch_main(self, argv, client, log_level, debug=False): |
243 | + env = SimpleEnvironment(argv[0], {"type": "ec2"}) |
244 | + client.env = env |
245 | + with patch("assess_container_networking.configure_logging", |
246 | + autospec=True) as mock_cl: |
247 | + with patch("jujupy.SimpleEnvironment.from_config", |
248 | + return_value=env) as mock_e: |
249 | + with patch("jujupy.EnvJujuClient.by_version", |
250 | + return_value=client) as mock_c: |
251 | + yield |
252 | + mock_cl.assert_called_once_with(log_level) |
253 | + mock_e.assert_called_once_with(argv[0]) |
254 | + mock_c.assert_called_once_with(env, argv[1], debug=debug) |
255 | + |
256 | + @contextmanager |
257 | + def patch_bootstrap_manager(self, runs=True): |
258 | + with patch("deploy_stack.BootstrapManager.top_context", |
259 | + autospec=True) as mock_tc: |
260 | + with patch("deploy_stack.BootstrapManager.bootstrap_context", |
261 | + autospec=True) as mock_bc: |
262 | + with patch("deploy_stack.BootstrapManager.runtime_context", |
263 | + autospec=True) as mock_rc: |
264 | + yield mock_bc |
265 | + self.assertEqual(mock_tc.call_count, 1) |
266 | + if runs: |
267 | + self.assertEqual(mock_rc.call_count, 1) |
268 | + |
269 | + def test_bootstrap_required(self): |
270 | + argv = ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod", "--verbose"] |
271 | + client = Mock(spec=["bootstrap", "enable_container_address_allocation", |
272 | + "is_jes_enabled"]) |
273 | + with patch("assess_container_networking.assess_container_networking", |
274 | + autospec=True) as mock_assess: |
275 | + with self.patch_bootstrap_manager() as mock_bc: |
276 | + with self.patch_main(argv, client, logging.DEBUG): |
277 | + ret = jcnet.main(argv) |
278 | + client.enable_container_address_allocation.assert_called_once_with() |
279 | + client.bootstrap.assert_called_once_with(False) |
280 | + self.assertEqual("", self.log_stream.getvalue()) |
281 | + self.assertEqual(mock_bc.call_count, 1) |
282 | + mock_assess.assert_called_once_with(client, None) |
283 | + self.assertEqual(ret, 0) |
284 | + |
285 | + def test_clean_existing_env(self): |
286 | + argv = ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod", |
287 | + "--clean-environment"] |
288 | + client = Mock(spec=["enable_container_address_allocation", "env", |
289 | + "get_status", "is_jes_enabled", "wait_for", |
290 | + "wait_for_started"]) |
291 | + client.get_status.return_value = Status.from_text(""" |
292 | + machines: |
293 | + "0": |
294 | + agent-state: started |
295 | + """) |
296 | + with patch("assess_container_networking.assess_container_networking", |
297 | + autospec=True) as mock_assess: |
298 | + with self.patch_bootstrap_manager() as mock_bc: |
299 | + with self.patch_main(argv, client, logging.INFO): |
300 | + ret = jcnet.main(argv) |
301 | + client.enable_container_address_allocation.assert_called_once_with() |
302 | + self.assertEqual(client.env.environment, "an-env-mod") |
303 | + self.assertEqual("", self.log_stream.getvalue()) |
304 | + self.assertEqual(mock_bc.call_count, 0) |
305 | + mock_assess.assert_called_once_with(client, None) |
306 | + self.assertEqual(ret, 0) |
307 | + |
308 | + def test_clean_missing_env(self): |
309 | + argv = ["an-env", "/bin/juju", "/tmp/logs", "an-env-mod", |
310 | + "--clean-environment"] |
311 | + client = Mock(spec=["bootstrap", "enable_container_address_allocation", |
312 | + "env", "get_status", "is_jes_enabled", "wait_for", |
313 | + "wait_for_started"]) |
314 | + client.get_status.side_effect = [ |
315 | + Exception("Timeout"), |
316 | + Status.from_text(""" |
317 | + machines: |
318 | + "0": |
319 | + agent-state: started |
320 | + """), |
321 | + ] |
322 | + with patch("assess_container_networking.assess_container_networking", |
323 | + autospec=True) as mock_assess: |
324 | + with self.patch_bootstrap_manager() as mock_bc: |
325 | + with self.patch_main(argv, client, logging.INFO): |
326 | + ret = jcnet.main(argv) |
327 | + client.enable_container_address_allocation.assert_called_once_with() |
328 | + self.assertEqual(client.env.environment, "an-env-mod") |
329 | + client.bootstrap.assert_called_once_with(False) |
330 | + self.assertEqual( |
331 | + "INFO Could not clean existing env: Timeout\n", |
332 | + self.log_stream.getvalue()) |
333 | + self.assertEqual(mock_bc.call_count, 1) |
334 | + mock_assess.assert_called_once_with(client, None) |
335 | + self.assertEqual(ret, 0) |
Looks reasonable to me, except I don't understand the update_env call. Shame I didn't ask about why you needed it on the call, but now I can't see why you'd need it.
When you create the client for --clean- environment, you want to be using the actual previous configuration, so that you get the right API server addresses. Since that actual previous configuration used the temp-env-name, presumably you'd need to load the environment using the temp-env-name. And in that case, the environment shouldn't need updating at all (because it was created using update_env).