Merge lp:~mskalka/juju-ci-tools/better-network-checking into lp:juju-ci-tools
- better-network-checking
- Merge into trunk
Proposed by
Michael Skalka
Status: | Merged |
---|---|
Merged at revision: | 1939 |
Proposed branch: | lp:~mskalka/juju-ci-tools/better-network-checking |
Merge into: | lp:juju-ci-tools |
Prerequisite: | lp:~mskalka/juju-ci-tools/update-nh-charm |
Diff against target: |
341 lines (+68/-106) 2 files modified
assess_network_health.py (+49/-71) tests/test_assess_network_health.py (+19/-35) |
To merge this branch: | bzr merge lp:~mskalka/juju-ci-tools/better-network-checking |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
Christopher Lee (community) | Needs Information | ||
Review via email: mp+320115@code.launchpad.net |
Commit message
rework neighbor visibility
Description of the change
Changes the behavior of neighbor_visibility to attempt to curl a simple http server on an associated network-health charm, which better mimics actual network behavior a charm would generate. This also makes the test compatible with public clouds.
Also removes the controller visibility test as this is not a supported feature of juju and does a few small re-orgs.
To post a comment you must log in.
Revision history for this message
Michael Skalka (mskalka) : | # |
- 1940. By Michael Skalka
-
cleanup as per MP comments
Revision history for this message
Curtis Hovey (sinzui) wrote : | # |
Thank you. We spoke and agreed that port 80 risks conflicts with other services so we will switch to a less popular port in a future branch.
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'assess_network_health.py' |
2 | --- assess_network_health.py 2017-03-10 15:43:14 +0000 |
3 | +++ assess_network_health.py 2017-03-17 14:19:58 +0000 |
4 | @@ -7,12 +7,12 @@ |
5 | import sys |
6 | import json |
7 | import yaml |
8 | -import ast |
9 | import subprocess |
10 | import re |
11 | import time |
12 | import os |
13 | import socket |
14 | +from collections import defaultdict |
15 | |
16 | from jujupy import ( |
17 | client_for_existing, |
18 | @@ -91,11 +91,6 @@ |
19 | interface_info = self.get_unit_info(client) |
20 | log.info('{0}Interface information:\n{1}'.format( |
21 | reboot_msg, json.dumps(interface_info, indent=4, sort_keys=True))) |
22 | - con_result = self.juju_controller_visibility(client) |
23 | - log.info('{0}Controller Visibility ' |
24 | - 'result:\n {1}'.format(reboot_msg, |
25 | - json.dumps(con_result, indent=4, |
26 | - sort_keys=True))) |
27 | int_result = self.internet_connection(client) |
28 | log.info('{0}Internet Test ' |
29 | 'result:\n {1}'.format(reboot_msg, |
30 | @@ -117,7 +112,7 @@ |
31 | sort_keys=True)) or |
32 | NO_EXPOSED_UNITS) |
33 | log.info('Tests complete.') |
34 | - return self.parse_final_results(con_result, vis_result, int_result, |
35 | + return self.parse_final_results(vis_result, int_result, |
36 | exp_result) |
37 | |
38 | def setup_testing_environment(self, client, bundle, target_model, |
39 | @@ -142,11 +137,14 @@ |
40 | try: |
41 | client.deploy('~juju-qa/network-health', series=series, |
42 | alias='network-health-{}'.format(series)) |
43 | + |
44 | except subprocess.CalledProcessError: |
45 | log.info('Could not deploy network-health-{} as it is already' |
46 | ' present in the juju deployment.'.format(series)) |
47 | client.wait_for_started() |
48 | client.wait_for_workloads() |
49 | + for series in self.existing_series: |
50 | + client.juju('expose', ('network-health-{}'.format(series))) |
51 | apps = client.get_status().get_applications() |
52 | log.info('Known applications: {}'.format(apps.keys())) |
53 | for app, info in apps.items(): |
54 | @@ -207,48 +205,23 @@ |
55 | |
56 | :param client: Client to get results from |
57 | :return: Dict of machine results as |
58 | - <machine>:{'dns':<dns>, 'interfaces':<interfaces>} |
59 | + <machine>:{'interfaces':<interfaces>} |
60 | """ |
61 | results = {} |
62 | apps = client.get_status().get_applications() |
63 | - nh_units = self.get_nh_units(apps, by_unit=True) |
64 | - for app, unit in nh_units.items(): |
65 | + nh_units = self.get_nh_unit_info(apps, by_unit=True) |
66 | + for app, units in nh_units.items(): |
67 | machine = apps[app.split('/')[0]]['units'][app]['machine'] |
68 | - results[machine] = {} |
69 | - out = client.action_do(unit[0], 'unit-info') |
70 | - out = client.action_fetch(out) |
71 | - out = yaml.safe_load(out) |
72 | - results[machine]['dns'] = out['results']['dns'] |
73 | - results[machine]['interfaces'] = out['results']['interfaces'] |
74 | + results[machine] = defaultdict(defaultdict) |
75 | + results[machine]['interfaces'] = {} |
76 | + for nh_unit in units.keys(): |
77 | + out = client.action_do(nh_unit, 'unit-info') |
78 | + out = client.action_fetch(out) |
79 | + out = yaml.safe_load(out) |
80 | + interfaces = out['results']['interfaces'] |
81 | + results[machine]['interfaces'][nh_unit] = interfaces |
82 | return results |
83 | |
84 | - def juju_controller_visibility(self, client): |
85 | - """Determine if known juju machines are visible from controller. |
86 | - |
87 | - :param machine: List of machine IPs to test |
88 | - :return: Connection attempt results |
89 | - """ |
90 | - cont_client = client.get_controller_client() |
91 | - log.info('Starting controller visibility test') |
92 | - machines = client.get_status().iter_machines(containers=True) |
93 | - result = {} |
94 | - for machine, info in machines: |
95 | - result[machine] = {} |
96 | - for ip in info['ip-addresses']: |
97 | - if self.is_ipv6(ip): |
98 | - cmd = 'ping6' |
99 | - else: |
100 | - cmd = 'ping' |
101 | - result[machine][ip] = False |
102 | - try: |
103 | - self.ssh(cont_client, '0', "{} -c 1 ".format(cmd) + ip) |
104 | - except subprocess.CalledProcessError as e: |
105 | - log.error('Error with ping attempt ' |
106 | - 'to {}: {}'.format(ip, e)) |
107 | - continue |
108 | - result[machine][ip] = True |
109 | - return result |
110 | - |
111 | def internet_connection(self, client): |
112 | """Test that targets can ping their default route. |
113 | |
114 | @@ -284,17 +257,25 @@ |
115 | results[unit[0]] = True |
116 | return results |
117 | |
118 | - def get_nh_units(self, apps, by_unit=False): |
119 | - nh_units = [] |
120 | - subs_by_unit = {} |
121 | - for service, s_info in apps.items(): |
122 | - for unit, u_info in s_info.get('units', {}).items(): |
123 | - nh_subs = [u for u in u_info.get('subordinates').keys() |
124 | - if 'network-health' in u] |
125 | - subs_by_unit[unit] = nh_subs |
126 | - nh_units.extend(nh_subs) |
127 | + def get_nh_unit_info(self, apps, by_unit=False): |
128 | + """Parses juju status information to return deployed network-health units. |
129 | + |
130 | + :param apps: Dict of apps given by get_status().get_applications() |
131 | + :param by_unit: Bool, returns dict of NH units keyed by the unit they |
132 | + are subordinate to |
133 | + :return: Dict of network-health units |
134 | + """ |
135 | + nh_units = {} |
136 | + nh_by_unit = {} |
137 | + for app, units in apps.items(): |
138 | + for unit, info in units.get('units', {}).items(): |
139 | + nh_by_unit[unit] = {} |
140 | + for sub, sub_info in info.get('subordinates', {}).items(): |
141 | + if 'network-health' in sub: |
142 | + nh_by_unit[unit][sub] = sub_info |
143 | + nh_units[sub] = sub_info |
144 | if by_unit: |
145 | - return subs_by_unit |
146 | + return nh_by_unit |
147 | return nh_units |
148 | |
149 | def neighbor_visibility(self, client): |
150 | @@ -304,15 +285,20 @@ |
151 | """ |
152 | log.info('Starting neighbor visibility test') |
153 | apps = client.get_status().get_applications() |
154 | - targets = self.parse_targets(client.get_status()) |
155 | + nh_units = self.get_nh_unit_info(apps) |
156 | + target_ips = [ip['public-address'] for ip in nh_units.values()] |
157 | result = {} |
158 | - nh_units = self.get_nh_units(apps) |
159 | - for nh_unit in nh_units: |
160 | - service_results = {} |
161 | - for service, units in targets.items(): |
162 | - res = self.ping_units(client, nh_unit, units) |
163 | - service_results[service] = ast.literal_eval(res) |
164 | - result[nh_unit] = service_results |
165 | + for app, units in apps.items(): |
166 | + result[app] = defaultdict(defaultdict) |
167 | + for unit, info in units.get('units', {}).items(): |
168 | + for ip in target_ips: |
169 | + result[app][unit][ip] = False |
170 | + pattern = r"(pass)" |
171 | + out = client.run(['curl {}:80'.format(ip)], |
172 | + units=[unit]) |
173 | + match = re.search(pattern, json.dumps(out[0])) |
174 | + if match: |
175 | + result[app][unit][ip] = True |
176 | return result |
177 | |
178 | def ensure_exposed(self, client, series): |
179 | @@ -377,28 +363,20 @@ |
180 | result['fail'] = result['fail'] + (service,) |
181 | return result |
182 | |
183 | - def parse_final_results(self, controller, visibility, internet, |
184 | - exposed): |
185 | + def parse_final_results(self, visibility, internet, exposed): |
186 | """Parses test results and raises an error if any failed. |
187 | |
188 | - :param controller: Controller test result |
189 | :param visibility: Visibility test result |
190 | :param exposed: Exposure test result |
191 | """ |
192 | log.info('Parsing final results.') |
193 | error_string = [] |
194 | - for machine, machine_result in controller.items(): |
195 | - for ip, res in machine_result.items(): |
196 | - if res is False: |
197 | - error = ('Failed to contact controller from machine {0} ' |
198 | - 'at address {1}'.format(machine, ip)) |
199 | - error_string.append(error) |
200 | for nh_source, service_result in visibility.items(): |
201 | for service, unit_res in service_result.items(): |
202 | if False in unit_res.values(): |
203 | failed = [u for u, r in unit_res.items() if r is False] |
204 | error = ('NH-Unit {0} failed to contact ' |
205 | - 'unit(s): {1}'.format(nh_source, failed)) |
206 | + 'targets(s): {1}'.format(nh_source, failed)) |
207 | error_string.append(error) |
208 | for unit, res in internet.items(): |
209 | if not res: |
210 | |
211 | === modified file 'tests/test_assess_network_health.py' |
212 | --- tests/test_assess_network_health.py 2017-03-10 15:43:14 +0000 |
213 | +++ tests/test_assess_network_health.py 2017-03-17 14:19:58 +0000 |
214 | @@ -95,6 +95,7 @@ |
215 | juju-status: |
216 | current: idle |
217 | public-address: 1.1.1.1 |
218 | + machine: '0' |
219 | ubuntu/1: |
220 | subordinates: |
221 | network-health/1: |
222 | @@ -104,6 +105,7 @@ |
223 | juju-status: |
224 | current: idle |
225 | public-address: 1.1.1.2 |
226 | + machine: '1' |
227 | application-status: |
228 | current: unknown |
229 | since: 01 Jan 2017 00:00:00-00:00 |
230 | @@ -145,8 +147,9 @@ |
231 | timing: |
232 | completed: 2017-01-01 00:00:01 +0000 UTC |
233 | enqueued: 2017-01-01 00:00:01 +0000 UTC |
234 | - started: 2017-01-01 00:00:01 +0000 UTC |
235 | + started: 2017-01-01 00:00:01 +0000 UTC} |
236 | """) |
237 | +curl_result = [{'foo': 'pass'}] |
238 | |
239 | dummy_charm = 'dummy' |
240 | series = 'trusty' |
241 | @@ -186,6 +189,7 @@ |
242 | alias='network-health-trusty', series='trusty'), |
243 | call.wait_for_started(), |
244 | call.wait_for_workloads(), |
245 | + call.juju('expose', 'network-health-trusty'), |
246 | call.get_status(), |
247 | call.juju('add-relation', ('ubuntu', 'network-health-trusty')), |
248 | call.juju('add-relation', ('network-health', |
249 | @@ -214,6 +218,7 @@ |
250 | alias='network-health-trusty', series='trusty'), |
251 | call.wait_for_started(), |
252 | call.wait_for_workloads(), |
253 | + call.juju('expose', 'network-health-trusty'), |
254 | call.get_status(), |
255 | call.juju('add-relation', ('ubuntu', 'network-health-trusty')), |
256 | call.juju('add-relation', ('network-health', |
257 | @@ -225,20 +230,6 @@ |
258 | 'network-health-trusty')], |
259 | client.mock_calls) |
260 | |
261 | - def test_juju_controller_visibility(self): |
262 | - args = self.parse_args([]) |
263 | - net_health = AssessNetworkHealth(args) |
264 | - client = fake_juju_client() |
265 | - client.bootstrap() |
266 | - now = datetime.now() + timedelta(days=1) |
267 | - with patch('utility.until_timeout.now', return_value=now): |
268 | - with patch.object(client, 'get_status', return_value=status): |
269 | - with patch('subprocess.check_output', |
270 | - return_value=0): |
271 | - out = net_health.juju_controller_visibility(client) |
272 | - expected = {'1': {'1.1.1.2': True}, '0': {'1.1.1.1': True}} |
273 | - self.assertEqual(expected, out) |
274 | - |
275 | def test_connect_to_existing_model_when_different(self): |
276 | model = {'bar': 'baz'} |
277 | args = self.parse_args([]) |
278 | @@ -267,20 +258,17 @@ |
279 | net_health = AssessNetworkHealth(args) |
280 | client = Mock(wraps=fake_juju_client()) |
281 | client.bootstrap() |
282 | - client._backend.set_action_result('network-health/0', 'ping', |
283 | - ping_result) |
284 | - client._backend.set_action_result('network-health/1', 'ping', |
285 | - ping_result) |
286 | now = datetime.now() + timedelta(days=1) |
287 | with patch('utility.until_timeout.now', return_value=now): |
288 | with patch.object(client, 'get_status', return_value=status): |
289 | - client.deploy('ubuntu', num=2, series='trusty') |
290 | - client.deploy('network-health', series='trusty') |
291 | - out = net_health.neighbor_visibility(client) |
292 | - expected = {'network-health/0': {'ubuntu': {u'ubuntu/0': True, |
293 | - u'ubuntu/1': True}}, |
294 | - 'network-health/1': {'ubuntu': {u'ubuntu/0': True, |
295 | - u'ubuntu/1': True}}} |
296 | + with patch.object(client, 'run', return_value=curl_result): |
297 | + client.deploy('ubuntu', num=2, series='trusty') |
298 | + client.deploy('network-health', series='trusty') |
299 | + out = net_health.neighbor_visibility(client) |
300 | + expected = {'network-health': {}, |
301 | + 'ubuntu': {'ubuntu/0': {'1.1.1.1': True, '1.1.1.2': True}, |
302 | + 'ubuntu/1': {'1.1.1.1': True, '1.1.1.2': True}}} |
303 | + |
304 | self.assertEqual(expected, out) |
305 | |
306 | def test_internet_connection(self): |
307 | @@ -399,17 +387,14 @@ |
308 | def test_parse_final_results_with_fail(self): |
309 | args = self.parse_args([]) |
310 | net_health = AssessNetworkHealth(args) |
311 | - controller = {"0": {"1.1.1.1": False}, |
312 | - "1": {"1.1.1.2": True}} |
313 | visible = {"bar/0": {"foo": {"foo/0": False, "foo/1": True}}} |
314 | internet = {"0": False, "1": True} |
315 | exposed = {"fail": ("foo"), "pass": ("bar", "baz")} |
316 | - out = net_health.parse_final_results(controller, visible, internet, |
317 | + out = net_health.parse_final_results(visible, internet, |
318 | exposed) |
319 | - error_strings = ["Failed to contact controller from machine 0 " |
320 | - "at address 1.1.1.1", |
321 | - "Machine 0 failed internet connection.", |
322 | - "NH-Unit bar/0 failed to contact unit(s): ['foo/0']", |
323 | + error_strings = ["Machine 0 failed internet connection.", |
324 | + "NH-Unit bar/0 failed to contact targets(s): " |
325 | + "['foo/0']", |
326 | "Application(s) foo failed expose test"] |
327 | for line in out: |
328 | self.assertTrue(line in error_strings) |
329 | @@ -417,11 +402,10 @@ |
330 | def test_parse_final_results_without_fail(self): |
331 | args = self.parse_args([]) |
332 | net_health = AssessNetworkHealth(args) |
333 | - controller = {"0": {"1.1.1.1": True}} |
334 | visible = {"bar/0": {"foo": {"foo/0": True, "foo/1": True}}} |
335 | internet = {"0": True, "1": True} |
336 | exposed = {"fail": (), "pass": ("foo", "bar", "baz")} |
337 | - net_health.parse_final_results(controller, visible, internet, exposed) |
338 | + net_health.parse_final_results(visible, internet, exposed) |
339 | |
340 | def test_ping_units(self): |
341 | args = self.parse_args([]) |
A couple of queries inline, also some suggestions.