Merge lp:~mskalka/juju-ci-tools/better-network-checking into lp:juju-ci-tools

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
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
Christopher Lee (veebers) wrote :

A couple of queries inline, also some suggestions.

review: Needs Information
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([])

Subscribers

People subscribed via source and target branches