Merge lp:~gnuoy/charm-helpers/neutron-shuffle into lp:charm-helpers
- neutron-shuffle
- Merge into devel
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 |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| charmers | 2015-03-24 | Pending | |
|
Review via email:
|
|||
Commit Message
Description of the Change
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) |

