Merge lp:~james-page/charm-helpers/get_network_addresses-refactor into lp:charm-helpers

Proposed by James Page
Status: Merged
Merged at revision: 780
Proposed branch: lp:~james-page/charm-helpers/get_network_addresses-refactor
Merge into: lp:charm-helpers
Diff against target: 573 lines (+137/-347)
2 files modified
charmhelpers/contrib/openstack/context.py (+25/-32)
tests/contrib/openstack/test_os_contexts.py (+112/-315)
To merge this branch: bzr merge lp:~james-page/charm-helpers/get_network_addresses-refactor
Reviewer Review Type Date Requested Status
David Ames (community) Approve
Alex Kavanagh Approve
Review via email: mp+328648@code.launchpad.net

Description of the change

Misc refactoring to support network spaces for Apache SSL support in the
openstack charms.

To post a comment you must log in.
Revision history for this message
Alex Kavanagh (ajkavanagh) wrote :

I'd also like David (thedac) to review this as he's spent a lot of time in this area recently.

review: Approve
Revision history for this message
James Page (james-page) wrote :

I think this code path is also a potential trip hazard for the dual-stack endpoint work that thedac is looking at - specifically combined with HTTPS; the code forces virtual host binding to specific network addresses and there is no concept of 'multiple addresses per endpoint' at the moment.

Revision history for this message
David Ames (thedac) wrote :

James,

Sorry for the long delay on this. I have finally completed some testing. My original concern was the removal of vip information. But I have proven to myself that
  endpoint = resolve_address(net_type)
Does the right thing.

Approved. Merging.

I'll also look closely at this for the dual stack bits.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/context.py'
2--- charmhelpers/contrib/openstack/context.py 2017-08-05 10:55:03 +0000
3+++ charmhelpers/contrib/openstack/context.py 2017-08-07 08:24:26 +0000
4@@ -41,9 +41,9 @@
5 charm_name,
6 DEBUG,
7 INFO,
8- WARNING,
9 ERROR,
10 status_set,
11+ network_get_primary_address
12 )
13
14 from charmhelpers.core.sysctl import create as sysctl_create
15@@ -80,6 +80,9 @@
16 from charmhelpers.contrib.openstack.ip import (
17 resolve_address,
18 INTERNAL,
19+ ADMIN,
20+ PUBLIC,
21+ ADDRESS_MAP,
22 )
23 from charmhelpers.contrib.network.ip import (
24 get_address_in_network,
25@@ -87,7 +90,6 @@
26 get_ipv6_addr,
27 get_netmask_for_address,
28 format_ipv6_addr,
29- is_address_in_network,
30 is_bridge_member,
31 is_ipv6_disabled,
32 )
33@@ -758,36 +760,27 @@
34 ...]
35 """
36 addresses = []
37- if config('vip'):
38- vips = config('vip').split()
39- else:
40- vips = []
41-
42- for net_type in ['internal', 'admin', 'public']:
43- net_config = config('os-{}-network'.format(net_type))
44- addr = get_address_in_network(net_config,
45- unit_get('private-address'))
46-
47- hostname_config = config('os-{}-hostname'.format(net_type))
48- if hostname_config:
49- addresses.append((addr, hostname_config))
50- elif len(vips) > 1 and is_clustered():
51- if not net_config:
52- log("Multiple networks configured but net_type "
53- "is None (%s)." % net_type, level=WARNING)
54- continue
55-
56- for vip in vips:
57- if is_address_in_network(net_config, vip):
58- addresses.append((addr, vip))
59- break
60-
61- elif is_clustered() and config('vip'):
62- addresses.append((addr, config('vip')))
63+ for net_type in [INTERNAL, ADMIN, PUBLIC]:
64+ net_config = config(ADDRESS_MAP[net_type]['config'])
65+ # NOTE(jamespage): Fallback must always be private address
66+ # as this is used to bind services on the
67+ # local unit.
68+ fallback = unit_get("private-address")
69+ if net_config:
70+ addr = get_address_in_network(net_config,
71+ fallback)
72 else:
73- addresses.append((addr, addr))
74-
75- return sorted(addresses)
76+ try:
77+ addr = network_get_primary_address(
78+ ADDRESS_MAP[net_type]['binding']
79+ )
80+ except NotImplementedError:
81+ addr = fallback
82+
83+ endpoint = resolve_address(net_type)
84+ addresses.append((addr, endpoint))
85+
86+ return sorted(set(addresses))
87
88 def __call__(self):
89 if isinstance(self.external_ports, six.string_types):
90@@ -814,7 +807,7 @@
91 self.configure_cert(cn)
92
93 addresses = self.get_network_addresses()
94- for address, endpoint in sorted(set(addresses)):
95+ for address, endpoint in addresses:
96 for api_port in self.external_ports:
97 ext_port = determine_apache_port(api_port,
98 singlenode_mode=True)
99
100=== modified file 'tests/contrib/openstack/test_os_contexts.py'
101--- tests/contrib/openstack/test_os_contexts.py 2017-08-05 10:55:03 +0000
102+++ tests/contrib/openstack/test_os_contexts.py 2017-08-07 08:24:26 +0000
103@@ -617,6 +617,8 @@
104 'pwgen',
105 'lsb_release',
106 'is_container',
107+ 'network_get_primary_address',
108+ 'resolve_address',
109 ]
110
111
112@@ -670,6 +672,8 @@
113 self.pwgen.return_value = 'testpassword'
114 self.lsb_release.return_value = {'DISTRIB_RELEASE': '16.04'}
115 self.is_container.return_value = False
116+ self.network_get_primary_address.side_effect = NotImplementedError()
117+ self.resolve_address.return_value = '10.5.1.50'
118
119 def _patch(self, method):
120 _m = patch('charmhelpers.contrib.openstack.context.' + method)
121@@ -1941,142 +1945,50 @@
122 self.https.return_value = False
123 self.assertEquals({}, apache())
124
125- @patch('charmhelpers.contrib.network.ip.is_address_in_network')
126- def _test_https_context(self, mock_is_address_in_network, apache,
127- is_clustered, peer_units,
128- network_config=NONET_CONFIG, multinet=False,
129- cn_provided=True):
130+ def test_https_context(self):
131 self.https.return_value = True
132- vips = network_config['vip'].split()
133- if multinet:
134- self.get_address_in_network.side_effect = ['10.5.1.100',
135- '10.5.2.100',
136- '10.5.3.100']
137- else:
138- self.get_address_in_network.return_value = 'cinderhost1'
139-
140- config = {}
141- config.update(network_config)
142- self.config.side_effect = lambda key: config.get(key)
143-
144- self.unit_get.return_value = 'cinderhost1'
145- self.is_clustered.return_value = is_clustered
146+ self.determine_api_port.return_value = 8756
147+ self.determine_apache_port.return_value = 8766
148
149 apache = context.ApacheSSLContext()
150 apache.configure_cert = MagicMock()
151 apache.enable_modules = MagicMock()
152 apache.configure_ca = MagicMock()
153- apache.canonical_names = MagicMock(return_value=[])
154-
155- if is_clustered:
156- if cn_provided:
157- apache.canonical_names.return_value = \
158- network_config['vip'].split()
159-
160- self.determine_api_port.return_value = 8756
161- self.determine_apache_port.return_value = 8766
162- if len(vips) > 1:
163- mock_is_address_in_network.side_effect = [
164- True, False, True, False, False, True
165- ]
166- else:
167- mock_is_address_in_network.return_value = True
168- else:
169- if cn_provided:
170- apache.canonical_names.return_value = ['cinderhost1']
171-
172- self.determine_api_port.return_value = 8766
173- self.determine_apache_port.return_value = 8776
174-
175+ apache.canonical_names = MagicMock()
176+ apache.canonical_names.return_value = [
177+ '10.5.1.1',
178+ '10.5.2.1',
179+ '10.5.3.1',
180+ ]
181+ apache.get_network_addresses = MagicMock()
182+ apache.get_network_addresses.return_value = [
183+ ('10.5.1.100', '10.5.1.1'),
184+ ('10.5.2.100', '10.5.2.1'),
185+ ('10.5.3.100', '10.5.3.1'),
186+ ]
187 apache.external_ports = '8776'
188 apache.service_namespace = 'cinder'
189
190- if is_clustered:
191- if len(vips) > 1:
192- ex = {
193- 'namespace': 'cinder',
194- 'endpoints': [('10.5.1.100', '10.5.1.1', 8766, 8756),
195- ('10.5.2.100', '10.5.2.1', 8766, 8756),
196- ('10.5.3.100', '10.5.3.1', 8766, 8756)],
197- 'ext_ports': [8766]
198- }
199- else:
200- ex = {
201- 'namespace': 'cinder',
202- 'endpoints': [('cinderhost1', 'cinderhost1vip',
203- 8766, 8756)],
204- 'ext_ports': [8766]
205- }
206- else:
207- if multinet:
208- ex = {
209- 'namespace': 'cinder',
210- 'endpoints': sorted([
211- ('10.5.3.100', '10.5.3.100', 8776, 8766),
212- ('10.5.2.100', '10.5.2.100', 8776, 8766),
213- ('10.5.1.100', '10.5.1.100', 8776, 8766)]),
214- 'ext_ports': [8776]
215- }
216- else:
217- ex = {
218- 'namespace': 'cinder',
219- 'endpoints': [('cinderhost1', 'cinderhost1', 8776, 8766)],
220- 'ext_ports': [8776]
221- }
222+ ex = {
223+ 'namespace': 'cinder',
224+ 'endpoints': [('10.5.1.100', '10.5.1.1', 8766, 8756),
225+ ('10.5.2.100', '10.5.2.1', 8766, 8756),
226+ ('10.5.3.100', '10.5.3.1', 8766, 8756)],
227+ 'ext_ports': [8766]
228+ }
229
230 self.assertEquals(ex, apache())
231- if is_clustered:
232- if len(vips) > 1:
233- apache.configure_cert.assert_has_calls([
234- call('10.5.1.1'),
235- call('10.5.2.1'),
236- call('10.5.3.1')
237- ])
238- else:
239- apache.configure_cert.assert_called_with('cinderhost1vip')
240- else:
241- if cn_provided:
242- apache.configure_cert.assert_called_with('cinderhost1')
243- else:
244- apache.configure_cert.assert_called_with('10.0.0.1')
245+
246+ apache.configure_cert.assert_has_calls([
247+ call('10.5.1.1'),
248+ call('10.5.2.1'),
249+ call('10.5.3.1')
250+ ])
251
252 self.assertTrue(apache.configure_ca.called)
253 self.assertTrue(apache.enable_modules.called)
254 self.assertTrue(apache.configure_cert.called)
255
256- @patch.object(context, 'resolve_address')
257- def test_https_context_no_cn(self, mock_resolve_address):
258- '''Test apache2 https with no cn provided'''
259- mock_resolve_address.return_value = "10.0.0.1"
260- apache = context.ApacheSSLContext()
261- self._test_https_context(apache, is_clustered=False, peer_units=None,
262- cn_provided=False)
263-
264- def test_https_context_no_peers_no_cluster(self):
265- '''Test apache2 https on a single, unclustered unit'''
266- apache = context.ApacheSSLContext()
267- self._test_https_context(apache, is_clustered=False, peer_units=None)
268-
269- def test_https_context_multinetwork(self):
270- apache = context.ApacheSSLContext()
271- self._test_https_context(apache, is_clustered=False, peer_units=None,
272- network_config=FULLNET_CONFIG, multinet=True)
273-
274- def test_https_context_multinetwork_cluster(self):
275- apache = context.ApacheSSLContext()
276- self._test_https_context(apache, is_clustered=True, peer_units=None,
277- network_config=FULLNET_CONFIG, multinet=True)
278-
279- def test_https_context_wth_peers_no_cluster(self):
280- '''Test apache2 https on a unclustered unit with peers'''
281- apache = context.ApacheSSLContext()
282- self._test_https_context(apache, is_clustered=False, peer_units=[1, 2])
283-
284- def test_https_context_wth_peers_cluster(self):
285- '''Test apache2 https on a clustered unit with peers'''
286- apache = context.ApacheSSLContext()
287- self._test_https_context(apache, is_clustered=True, peer_units=[1, 2])
288-
289 def test_https_context_loads_correct_apache_mods(self):
290 '''Test apache2 context also loads required apache modules'''
291 apache = context.ApacheSSLContext()
292@@ -2810,201 +2722,86 @@
293 self.assertEqual(context.WorkerConfigContext()(),
294 {'workers': 256})
295
296- def test_apache_get_addresses_no_network_splits(self):
297- self.https.return_value = True
298- self.config.side_effect = fake_config({
299- 'vip': '10.5.1.1 10.5.2.1 10.5.3.1',
300- 'os-internal-network': None,
301- 'os-admin-network': None,
302- 'os-public-network': None
303- })
304- self.is_clustered.side_effect = [True, True, True]
305- self.get_address_in_network.side_effect = ['10.5.1.100',
306- '10.5.2.100',
307- '10.5.3.100']
308-
309- self.unit_get.return_value = '10.5.1.50'
310- apache = context.ApacheSSLContext()
311- apache.external_ports = '8776'
312-
313- addresses = apache.get_network_addresses()
314- expected = []
315- self.assertEqual(addresses, expected)
316-
317- calls = [call(None, '10.5.1.50'),
318- call(None, '10.5.1.50'),
319- call(None, '10.5.1.50')]
320- self.get_address_in_network.assert_has_calls(calls)
321-
322- def test_apache_get_addresses_no_vips_no_networks(self):
323- self.https.return_value = True
324- self.config.side_effect = fake_config({
325- 'vip': '',
326- 'os-internal-network': None,
327- 'os-admin-network': None,
328- 'os-public-network': None
329- })
330- self.is_clustered.side_effect = [True, True, True]
331- self.get_address_in_network.side_effect = ['10.5.1.100',
332- '10.5.2.100',
333- '10.5.3.100']
334-
335- self.unit_get.return_value = '10.5.1.50'
336- apache = context.ApacheSSLContext()
337-
338- addresses = apache.get_network_addresses()
339- expected = [('10.5.1.100', '10.5.1.100'),
340- ('10.5.2.100', '10.5.2.100'),
341- ('10.5.3.100', '10.5.3.100')]
342- self.assertEqual(addresses, expected)
343-
344- calls = [call(None, '10.5.1.50'),
345- call(None, '10.5.1.50'),
346- call(None, '10.5.1.50')]
347- self.get_address_in_network.assert_has_calls(calls)
348-
349- def test_apache_get_addresses_no_vips_w_networks(self):
350- self.https.return_value = True
351- self.config.side_effect = fake_config({
352- 'vip': '',
353- 'os-internal-network': '10.5.1.0/24',
354- 'os-admin-network': '10.5.2.0/24',
355- 'os-public-network': '10.5.3.0/24',
356- })
357- self.is_clustered.side_effect = [True, True, True]
358- self.get_address_in_network.side_effect = ['10.5.1.100',
359- '10.5.2.100',
360- '10.5.3.100']
361-
362- self.unit_get.return_value = '10.5.1.50'
363- apache = context.ApacheSSLContext()
364-
365- addresses = apache.get_network_addresses()
366- expected = [('10.5.1.100', '10.5.1.100'),
367- ('10.5.2.100', '10.5.2.100'),
368- ('10.5.3.100', '10.5.3.100')]
369- self.assertEqual(addresses, expected)
370-
371- calls = [call('10.5.1.0/24', '10.5.1.50'),
372- call('10.5.2.0/24', '10.5.1.50'),
373- call('10.5.3.0/24', '10.5.1.50')]
374- self.get_address_in_network.assert_has_calls(calls)
375-
376- def test_apache_get_addresses_with_network_splits(self):
377- self.https.return_value = True
378- self.config.side_effect = fake_config({
379- 'vip': '10.5.1.1 10.5.2.1 10.5.3.1',
380- 'os-internal-network': '10.5.1.0/24',
381- 'os-admin-network': '10.5.2.0/24',
382- 'os-public-network': '10.5.3.0/24',
383- })
384- self.is_clustered.side_effect = [True, True, True]
385- self.get_address_in_network.side_effect = ['10.5.1.100',
386- '10.5.2.100',
387- '10.5.3.100']
388-
389- self.unit_get.return_value = '10.5.1.50'
390- apache = context.ApacheSSLContext()
391- apache.external_ports = '8776'
392-
393- addresses = apache.get_network_addresses()
394- expected = [('10.5.1.100', '10.5.1.1'),
395- ('10.5.2.100', '10.5.2.1'),
396- ('10.5.3.100', '10.5.3.1')]
397-
398- self.assertEqual(addresses, expected)
399-
400- calls = [call('10.5.1.0/24', '10.5.1.50'),
401- call('10.5.2.0/24', '10.5.1.50'),
402- call('10.5.3.0/24', '10.5.1.50')]
403- self.get_address_in_network.assert_has_calls(calls)
404-
405- def test_apache_get_addresses_with_network_splits_and_hostnames(self):
406- self.https.return_value = True
407- self.config.side_effect = fake_config({
408- 'vip': '10.5.1.1 10.5.2.1 10.5.3.1',
409- 'os-internal-network': '10.5.1.0/24',
410- 'os-admin-network': '10.5.2.0/24',
411- 'os-public-network': '10.5.3.0/24',
412- 'os-internal-hostname': 'glance.internal',
413- 'os-admin-hostname': 'glance.admin',
414- 'os-public-hostname': 'glance.public',
415- })
416- self.is_clustered.side_effect = [True, True, True]
417- self.get_address_in_network.side_effect = ['10.5.1.100',
418- '10.5.2.100',
419- '10.5.3.100']
420-
421- self.unit_get.return_value = '10.5.1.50'
422- apache = context.ApacheSSLContext()
423- apache.external_ports = '8776'
424-
425- addresses = apache.get_network_addresses()
426- expected = [('10.5.1.100', 'glance.internal'),
427- ('10.5.2.100', 'glance.admin'),
428- ('10.5.3.100', 'glance.public')]
429-
430- self.assertEqual(addresses, expected)
431-
432- calls = [call('10.5.1.0/24', '10.5.1.50'),
433- call('10.5.2.0/24', '10.5.1.50'),
434- call('10.5.3.0/24', '10.5.1.50')]
435- self.get_address_in_network.assert_has_calls(calls)
436-
437- def test_apache_get_addresses_with_missing_network(self):
438- self.https.return_value = True
439- self.config.side_effect = fake_config({
440- 'vip': '10.5.1.1 10.5.2.1 10.5.3.1',
441- 'os-internal-network': '10.5.1.0/24',
442- 'os-admin-network': '10.5.2.0/24',
443- })
444- self.is_clustered.side_effect = [True, True, True]
445- self.get_address_in_network.side_effect = ['10.5.1.100',
446- '10.5.2.100',
447- '10.5.1.50']
448-
449- self.unit_get.return_value = '10.5.1.50'
450- apache = context.ApacheSSLContext()
451- apache.external_ports = '8776'
452-
453- addresses = apache.get_network_addresses()
454- expected = [('10.5.1.100', '10.5.1.1'),
455- ('10.5.2.100', '10.5.2.1')]
456-
457- self.assertEqual(addresses, expected)
458-
459- calls = [call('10.5.1.0/24', '10.5.1.50'),
460- call('10.5.2.0/24', '10.5.1.50')]
461- self.get_address_in_network.assert_has_calls(calls)
462-
463- def test_apache_get_addresses_with_network_splits_ipv6(self):
464- self.https.return_value = True
465- self.config.side_effect = fake_config({
466- 'vip': ('2001:db8::5001 2001:db9::5001 2001:dba::5001'),
467- 'os-internal-network': '2001:db8::/113',
468- 'os-admin-network': '2001:db9::/113',
469- 'os-public-network': '2001:dba::/113',
470- })
471- self.is_clustered.side_effect = [True, True, True]
472- self.get_address_in_network.side_effect = ['2001:db8::5100',
473- '2001:db9::5100',
474- '2001:dba::5100']
475-
476- self.unit_get.return_value = '2001:db8::5050'
477- apache = context.ApacheSSLContext()
478- apache.external_ports = '8776'
479-
480- addresses = apache.get_network_addresses()
481- expected = [('2001:db8::5100', '2001:db8::5001'),
482- ('2001:db9::5100', '2001:db9::5001'),
483- ('2001:dba::5100', '2001:dba::5001')]
484-
485- self.assertEqual(addresses, expected)
486-
487- calls = [call('2001:db8::/113', '2001:db8::5050'),
488- call('2001:db9::/113', '2001:db8::5050'),
489- call('2001:dba::/113', '2001:db8::5050')]
490- self.get_address_in_network.assert_has_calls(calls)
491+ def test_apache_get_addresses_no_network_config(self):
492+ self.config.side_effect = fake_config({
493+ 'os-internal-network': None,
494+ 'os-admin-network': None,
495+ 'os-public-network': None
496+ })
497+ self.resolve_address.return_value = '10.5.1.50'
498+ self.unit_get.return_value = '10.5.1.50'
499+
500+ apache = context.ApacheSSLContext()
501+ apache.external_ports = '8776'
502+
503+ addresses = apache.get_network_addresses()
504+ expected = [('10.5.1.50', '10.5.1.50')]
505+
506+ self.assertEqual(addresses, expected)
507+
508+ self.get_address_in_network.assert_not_called()
509+ self.resolve_address.assert_has_calls([
510+ call(context.INTERNAL),
511+ call(context.ADMIN),
512+ call(context.PUBLIC)
513+ ])
514+
515+ def test_apache_get_addresses_with_network_config(self):
516+ self.config.side_effect = fake_config({
517+ 'os-internal-network': '10.5.1.0/24',
518+ 'os-admin-network': '10.5.2.0/24',
519+ 'os-public-network': '10.5.3.0/24',
520+ })
521+ _base_addresses = ['10.5.1.100',
522+ '10.5.2.100',
523+ '10.5.3.100']
524+ self.get_address_in_network.side_effect = _base_addresses
525+ self.resolve_address.side_effect = _base_addresses
526+ self.unit_get.return_value = '10.5.1.50'
527+
528+ apache = context.ApacheSSLContext()
529+
530+ addresses = apache.get_network_addresses()
531+ expected = [('10.5.1.100', '10.5.1.100'),
532+ ('10.5.2.100', '10.5.2.100'),
533+ ('10.5.3.100', '10.5.3.100')]
534+ self.assertEqual(addresses, expected)
535+
536+ calls = [call('10.5.1.0/24', '10.5.1.50'),
537+ call('10.5.2.0/24', '10.5.1.50'),
538+ call('10.5.3.0/24', '10.5.1.50')]
539+ self.get_address_in_network.assert_has_calls(calls)
540+ self.resolve_address.assert_has_calls([
541+ call(context.INTERNAL),
542+ call(context.ADMIN),
543+ call(context.PUBLIC)
544+ ])
545+
546+ def test_apache_get_addresses_network_spaces(self):
547+ self.config.side_effect = fake_config({
548+ 'os-internal-network': None,
549+ 'os-admin-network': None,
550+ 'os-public-network': None
551+ })
552+ self.network_get_primary_address.side_effect = None
553+ self.network_get_primary_address.return_value = '10.5.2.50'
554+ self.resolve_address.return_value = '10.5.2.100'
555+ self.unit_get.return_value = '10.5.1.50'
556+
557+ apache = context.ApacheSSLContext()
558+ apache.external_ports = '8776'
559+
560+ addresses = apache.get_network_addresses()
561+ expected = [('10.5.2.50', '10.5.2.100')]
562+
563+ self.assertEqual(addresses, expected)
564+
565+ self.get_address_in_network.assert_not_called()
566+ self.resolve_address.assert_has_calls([
567+ call(context.INTERNAL),
568+ call(context.ADMIN),
569+ call(context.PUBLIC)
570+ ])
571
572 def test_config_flag_parsing_simple(self):
573 # Standard key=value checks...

Subscribers

People subscribed via source and target branches