Merge lp:~gnuoy/charm-helpers/neutron-shuffle into lp:charm-helpers

Proposed by Liam Young on 2015-03-24
Status: Merged
Merged at revision: 343
Proposed branch: lp:~gnuoy/charm-helpers/neutron-shuffle
Merge into: lp:charm-helpers
Diff against target: 374 lines (+323/-1)
2 files modified
charmhelpers/contrib/openstack/context.py (+144/-1)
tests/contrib/openstack/test_os_contexts.py (+179/-0)
To merge this branch: bzr merge lp:~gnuoy/charm-helpers/neutron-shuffle
Reviewer Review Type Date Requested Status
charmers 2015-03-24 Pending
Review via email: mp+253958@code.launchpad.net
To post a comment you must log in.

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 2015-03-18 11:50:24 +0000
3+++ charmhelpers/contrib/openstack/context.py 2015-03-24 14:04:19 +0000
4@@ -47,6 +47,7 @@
5 )
6
7 from charmhelpers.core.sysctl import create as sysctl_create
8+from charmhelpers.core.strutils import bool_from_string
9
10 from charmhelpers.core.host import (
11 list_nics,
12@@ -67,6 +68,7 @@
13 )
14 from charmhelpers.contrib.openstack.neutron import (
15 neutron_plugin_attribute,
16+ parse_data_port_mappings,
17 )
18 from charmhelpers.contrib.openstack.ip import (
19 resolve_address,
20@@ -82,7 +84,6 @@
21 is_bridge_member,
22 )
23 from charmhelpers.contrib.openstack.utils import get_host_ip
24-
25 CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
26 ADDRESS_TYPES = ['admin', 'internal', 'public']
27
28@@ -1162,3 +1163,145 @@
29 sysctl_create(sysctl_dict,
30 '/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
31 return {'sysctl': sysctl_dict}
32+
33+
34+class NeutronAPIContext(OSContextGenerator):
35+ '''
36+ Inspects current neutron-plugin-api relation for neutron settings. Return
37+ defaults if it is not present.
38+ '''
39+ interfaces = ['neutron-plugin-api']
40+
41+ def __call__(self):
42+ self.neutron_defaults = {
43+ 'l2_population': {
44+ 'rel_key': 'l2-population',
45+ 'default': False,
46+ },
47+ 'overlay_network_type': {
48+ 'rel_key': 'overlay-network-type',
49+ 'default': 'gre',
50+ },
51+ 'neutron_security_groups': {
52+ 'rel_key': 'neutron-security-groups',
53+ 'default': False,
54+ },
55+ 'network_device_mtu': {
56+ 'rel_key': 'network-device-mtu',
57+ 'default': None,
58+ },
59+ 'enable_dvr': {
60+ 'rel_key': 'enable-dvr',
61+ 'default': False,
62+ },
63+ 'enable_l3ha': {
64+ 'rel_key': 'enable-l3ha',
65+ 'default': False,
66+ },
67+ }
68+ ctxt = self.get_neutron_options({})
69+ for rid in relation_ids('neutron-plugin-api'):
70+ for unit in related_units(rid):
71+ rdata = relation_get(rid=rid, unit=unit)
72+ if 'l2-population' in rdata:
73+ ctxt.update(self.get_neutron_options(rdata))
74+
75+ return ctxt
76+
77+ def get_neutron_options(self, rdata):
78+ settings = {}
79+ for nkey in self.neutron_defaults.keys():
80+ defv = self.neutron_defaults[nkey]['default']
81+ rkey = self.neutron_defaults[nkey]['rel_key']
82+ if rkey in rdata.keys():
83+ if type(defv) is bool:
84+ settings[nkey] = bool_from_string(rdata[rkey])
85+ else:
86+ settings[nkey] = rdata[rkey]
87+ else:
88+ settings[nkey] = defv
89+ return settings
90+
91+
92+class ExternalPortContext(NeutronPortContext):
93+
94+ def __call__(self):
95+ ctxt = {}
96+ ports = config('ext-port')
97+ if ports:
98+ ports = [p.strip() for p in ports.split()]
99+ ports = self.resolve_ports(ports)
100+ if ports:
101+ ctxt = {"ext_port": ports[0]}
102+ napi_settings = NeutronAPIContext()()
103+ mtu = napi_settings.get('network_device_mtu')
104+ if mtu:
105+ ctxt['ext_port_mtu'] = mtu
106+
107+ return ctxt
108+
109+
110+class DataPortContext(NeutronPortContext):
111+
112+ def __call__(self):
113+ ports = config('data-port')
114+ if ports:
115+ portmap = parse_data_port_mappings(ports)
116+ ports = portmap.values()
117+ resolved = self.resolve_ports(ports)
118+ normalized = {get_nic_hwaddr(port): port for port in resolved
119+ if port not in ports}
120+ normalized.update({port: port for port in resolved
121+ if port in ports})
122+ if resolved:
123+ return {bridge: normalized[port] for bridge, port in
124+ six.iteritems(portmap) if port in normalized.keys()}
125+
126+ return None
127+
128+
129+class PhyNICMTUContext(DataPortContext):
130+
131+ def __call__(self):
132+ ctxt = {}
133+ mappings = super(PhyNICMTUContext, self).__call__()
134+ if mappings and mappings.values():
135+ ports = mappings.values()
136+ napi_settings = NeutronAPIContext()()
137+ mtu = napi_settings.get('network_device_mtu')
138+ if mtu:
139+ ctxt["devs"] = '\\n'.join(ports)
140+ ctxt['mtu'] = mtu
141+
142+ return ctxt
143+
144+
145+class NetworkServiceContext(OSContextGenerator):
146+
147+ def __init__(self, rel_name='quantum-network-service'):
148+ self.rel_name = rel_name
149+ self.interfaces = [rel_name]
150+
151+ def __call__(self):
152+ for rid in relation_ids(self.rel_name):
153+ for unit in related_units(rid):
154+ rdata = relation_get(rid=rid, unit=unit)
155+ ctxt = {
156+ 'keystone_host': rdata.get('keystone_host'),
157+ 'service_port': rdata.get('service_port'),
158+ 'auth_port': rdata.get('auth_port'),
159+ 'service_tenant': rdata.get('service_tenant'),
160+ 'service_username': rdata.get('service_username'),
161+ 'service_password': rdata.get('service_password'),
162+ 'quantum_host': rdata.get('quantum_host'),
163+ 'quantum_port': rdata.get('quantum_port'),
164+ 'quantum_url': rdata.get('quantum_url'),
165+ 'region': rdata.get('region'),
166+ 'service_protocol':
167+ rdata.get('service_protocol') or 'http',
168+ 'auth_protocol':
169+ rdata.get('auth_protocol') or 'http',
170+ }
171+ if context_complete(ctxt):
172+ return ctxt
173+ return {}
174
175=== modified file 'tests/contrib/openstack/test_os_contexts.py'
176--- tests/contrib/openstack/test_os_contexts.py 2015-03-18 11:50:24 +0000
177+++ tests/contrib/openstack/test_os_contexts.py 2015-03-24 14:04:19 +0000
178@@ -274,6 +274,23 @@
179 }
180 }
181
182+QUANTUM_NETWORK_SERVICE_RELATION = {
183+ 'quantum-network-service:0': {
184+ 'unit/0': {
185+ 'keystone_host': '10.5.0.1',
186+ 'service_port': '5000',
187+ 'auth_port': '20000',
188+ 'service_tenant': 'tenant',
189+ 'service_username': 'username',
190+ 'service_password': 'password',
191+ 'quantum_host': '10.5.0.2',
192+ 'quantum_port': '9696',
193+ 'quantum_url': 'http://10.5.0.2:9696/v2',
194+ 'region': 'aregion'
195+ },
196+ }
197+}
198+
199
200 SUB_CONFIG = """
201 nova:
202@@ -355,6 +372,22 @@
203 'os-public-network': "10.5.3.0/24"
204 }
205
206+MACHINE_MACS = {
207+ 'eth0': 'fe:c5:ce:8e:2b:00',
208+ 'eth1': 'fe:c5:ce:8e:2b:01',
209+ 'eth2': 'fe:c5:ce:8e:2b:02',
210+ 'eth3': 'fe:c5:ce:8e:2b:03',
211+}
212+
213+MACHINE_NICS = {
214+ 'eth0': ['192.168.0.1'],
215+ 'eth1': ['192.168.0.2'],
216+ 'eth2': [],
217+ 'eth3': [],
218+}
219+
220+ABSENT_MACS = "aa:a5:ae:ae:ab:a4 "
221+
222 # Imported in contexts.py and needs patching in setUp()
223 TO_PATCH = [
224 'b64decode',
225@@ -2231,3 +2264,149 @@
226 self.assertEqual(flags, {'user_tree_dn': ('ou=ABC General,'
227 'ou=User Accounts,'
228 'dc=example,dc=com')})
229+
230+ def _fake_get_hwaddr(self, arg):
231+ return MACHINE_MACS[arg]
232+
233+ def _fake_get_ipv4(self, arg, fatal=False):
234+ return MACHINE_NICS[arg]
235+
236+ @patch('charmhelpers.contrib.openstack.context.config')
237+ def test_no_ext_port(self, mock_config):
238+ self.config.side_effect = config = fake_config({})
239+ mock_config.side_effect = config
240+ self.assertEquals(context.ExternalPortContext()(), {})
241+
242+ @patch('charmhelpers.contrib.openstack.context.config')
243+ def test_ext_port_eth(self, mock_config):
244+ config = fake_config({'ext-port': 'eth1010'})
245+ self.config.side_effect = config
246+ mock_config.side_effect = config
247+ self.assertEquals(context.ExternalPortContext()(),
248+ {'ext_port': 'eth1010'})
249+
250+ @patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr')
251+ @patch('charmhelpers.contrib.openstack.context.list_nics')
252+ @patch('charmhelpers.contrib.openstack.context.get_ipv6_addr')
253+ @patch('charmhelpers.contrib.openstack.context.get_ipv4_addr')
254+ @patch('charmhelpers.contrib.openstack.context.config')
255+ def test_ext_port_mac(self, mock_config, mock_get_ipv4_addr,
256+ mock_get_ipv6_addr, mock_list_nics,
257+ mock_get_nic_hwaddr):
258+ config_macs = ABSENT_MACS + " " + MACHINE_MACS['eth2']
259+ config = fake_config({'ext-port': config_macs})
260+ self.config.side_effect = config
261+ mock_config.side_effect = config
262+
263+ mock_get_ipv4_addr.side_effect = self._fake_get_ipv4
264+ mock_get_ipv6_addr.return_value = []
265+ mock_list_nics.return_value = MACHINE_MACS.keys()
266+ mock_get_nic_hwaddr.side_effect = self._fake_get_hwaddr
267+
268+ self.assertEquals(context.ExternalPortContext()(),
269+ {'ext_port': 'eth2'})
270+
271+ config = fake_config({'ext-port': ABSENT_MACS})
272+ self.config.side_effect = config
273+ mock_config.side_effect = config
274+
275+ self.assertEquals(context.ExternalPortContext()(), {})
276+
277+ @patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr')
278+ @patch('charmhelpers.contrib.openstack.context.list_nics')
279+ @patch('charmhelpers.contrib.openstack.context.get_ipv6_addr')
280+ @patch('charmhelpers.contrib.openstack.context.get_ipv4_addr')
281+ @patch('charmhelpers.contrib.openstack.context.config')
282+ def test_ext_port_mac_one_used_nic(self, mock_config,
283+ mock_get_ipv4_addr,
284+ mock_get_ipv6_addr, mock_list_nics,
285+ mock_get_nic_hwaddr):
286+
287+ self.relation_ids.return_value = ['neutron-plugin-api:1']
288+ self.related_units.return_value = ['neutron-api/0']
289+ self.relation_get.return_value = {'network-device-mtu': 1234,
290+ 'l2-population': 'False'}
291+ config_macs = "%s %s" % (MACHINE_MACS['eth1'],
292+ MACHINE_MACS['eth2'])
293+
294+ mock_get_ipv4_addr.side_effect = self._fake_get_ipv4
295+ mock_get_ipv6_addr.return_value = []
296+ mock_list_nics.return_value = MACHINE_MACS.keys()
297+ mock_get_nic_hwaddr.side_effect = self._fake_get_hwaddr
298+
299+ config = fake_config({'ext-port': config_macs})
300+ self.config.side_effect = config
301+ mock_config.side_effect = config
302+ self.assertEquals(context.ExternalPortContext()(),
303+ {'ext_port': 'eth2', 'ext_port_mtu': 1234})
304+
305+ @patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
306+ 'resolve_ports')
307+ def test_data_port_eth(self, mock_resolve):
308+ self.config.side_effect = fake_config({'data-port':
309+ 'phybr1:eth1010'})
310+ mock_resolve.side_effect = lambda ports: ports
311+ self.assertEquals(context.DataPortContext()(),
312+ {'phybr1': 'eth1010'})
313+
314+ def test_neutronapicontext_defaults(self):
315+ self.relation_ids.return_value = []
316+ expected_keys = [
317+ 'l2_population', 'enable_dvr', 'enable_l3ha',
318+ 'overlay_network_type', 'network_device_mtu'
319+ ]
320+ api_ctxt = context.NeutronAPIContext()()
321+ for key in expected_keys:
322+ self.assertTrue(key in api_ctxt)
323+
324+ def test_neutronapicontext_string_converted(self):
325+ self.relation_ids.return_value = ['neutron-plugin-api:1']
326+ self.related_units.return_value = ['neutron-api/0']
327+ self.relation_get.return_value = {'l2-population': 'True'}
328+ api_ctxt = context.NeutronAPIContext()()
329+ self.assertEquals(api_ctxt['l2_population'], True)
330+
331+ def test_neutronapicontext_none(self):
332+ self.relation_ids.return_value = ['neutron-plugin-api:1']
333+ self.related_units.return_value = ['neutron-api/0']
334+ self.relation_get.return_value = {'l2-population': 'True'}
335+ api_ctxt = context.NeutronAPIContext()()
336+ self.assertEquals(api_ctxt['network_device_mtu'], None)
337+
338+ def test_network_service_ctxt_no_units(self):
339+ self.relation_ids.return_value = []
340+ self.relation_ids.return_value = ['foo']
341+ self.related_units.return_value = []
342+ self.assertEquals(context.NetworkServiceContext()(), {})
343+
344+ @patch.object(context, 'context_complete')
345+ def test_network_service_ctxt_no_data(self, mock_context_complete):
346+ rel = FakeRelation(QUANTUM_NETWORK_SERVICE_RELATION)
347+ self.relation_ids.side_effect = rel.relation_ids
348+ self.related_units.side_effect = rel.relation_units
349+ relation = FakeRelation(relation_data=QUANTUM_NETWORK_SERVICE_RELATION)
350+ self.relation_get.side_effect = relation.get
351+ mock_context_complete.return_value = False
352+ self.assertEquals(context.NetworkServiceContext()(), {})
353+
354+ def test_network_service_ctxt_data(self):
355+ data_result = {
356+ 'keystone_host': '10.5.0.1',
357+ 'service_port': '5000',
358+ 'auth_port': '20000',
359+ 'service_tenant': 'tenant',
360+ 'service_username': 'username',
361+ 'service_password': 'password',
362+ 'quantum_host': '10.5.0.2',
363+ 'quantum_port': '9696',
364+ 'quantum_url': 'http://10.5.0.2:9696/v2',
365+ 'region': 'aregion',
366+ 'service_protocol': 'http',
367+ 'auth_protocol': 'http',
368+ }
369+ rel = FakeRelation(QUANTUM_NETWORK_SERVICE_RELATION)
370+ self.relation_ids.side_effect = rel.relation_ids
371+ self.related_units.side_effect = rel.relation_units
372+ relation = FakeRelation(relation_data=QUANTUM_NETWORK_SERVICE_RELATION)
373+ self.relation_get.side_effect = relation.get
374+ self.assertEquals(context.NetworkServiceContext()(), data_result)

Subscribers

People subscribed via source and target branches