diff --git a/config.yaml b/config.yaml index b4938fc3..74473ae3 100755 --- a/config.yaml +++ b/config.yaml @@ -359,6 +359,12 @@ options: default: False description: | Enable QoS (assuming the plug-in or ml2 mechanism driver can support it) + enable-vlan-trunking: + type: boolean + default: False + description: | + Enable VLAN trunking or a VLAN aware VM. Neutron extension to access + lots of neutron networks over a single vNIC as tagged/encapsulated traffic. # Quota config quota-security-group: type: int diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index b196d63f..98a9d4a8 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -1506,6 +1506,10 @@ class NeutronAPIContext(OSContextGenerator): 'rel_key': 'enable-qos', 'default': False, }, + 'enable_vlan_trunking': { + 'rel_key': 'enable-vlan-trunking', + 'default': False, + }, } ctxt = self.get_neutron_options({}) for rid in relation_ids('neutron-plugin-api'): diff --git a/hooks/neutron_api_context.py b/hooks/neutron_api_context.py index 179c3f8a..4905a40e 100644 --- a/hooks/neutron_api_context.py +++ b/hooks/neutron_api_context.py @@ -218,6 +218,37 @@ def is_qos_requested_and_valid(): return False +def is_vlan_trunking_requested_and_valid(): + """Check whether VLAN trunking should be enabled by checking whether + it has been requested and, if it has, is it supported in the current + configuration. + """ + + if config('enable-vlan-trunking'): + if VLAN not in _get_tenant_network_types(): + msg = ("Disabling vlan-trunking, the vlan network type must be " + "enabled to use vlan-trunking") + log(msg, ERROR) + return False + + if config('neutron-plugin') != 'ovs': + msg = ("Disabling vlan-trunking, implementation only exists " + "for the OVS plugin") + log(msg, ERROR) + return False + + if CompareOpenStackReleases(os_release('neutron-server')) < 'newton': + msg = ("The vlan-trunking option is only supported on newton or " + "later") + log(msg, ERROR) + return False + print("release >= newton") + + return True + else: + return False + + class ApacheSSLContext(context.ApacheSSLContext): interfaces = ['https'] @@ -486,6 +517,10 @@ class NeutronCCContext(context.NeutronContext): if is_qos_requested_and_valid(): ctxt['service_plugins'].append('qos') + + if is_vlan_trunking_requested_and_valid(): + ctxt['service_plugins'].append('trunk') + ctxt['service_plugins'] = ','.join(ctxt['service_plugins']) return ctxt diff --git a/hooks/neutron_api_hooks.py b/hooks/neutron_api_hooks.py index 05cb79f7..a77c0c91 100755 --- a/hooks/neutron_api_hooks.py +++ b/hooks/neutron_api_hooks.py @@ -91,6 +91,7 @@ from neutron_api_context import ( get_overlay_network_type, IdentityServiceContext, is_qos_requested_and_valid, + is_vlan_trunking_requested_and_valid, EtcdContext, ) @@ -454,6 +455,7 @@ def neutron_plugin_api_relation_joined(rid=None): 'enable-dvr': get_dvr(), 'enable-l3ha': get_l3ha(), 'enable-qos': is_qos_requested_and_valid(), + 'enable-vlan-trunking': is_vlan_trunking_requested_and_valid(), 'overlay-network-type': get_overlay_network_type(), 'addr': unit_get('private-address'), 'polling-interval': config('polling-interval'), diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 2ad59573..cb5befae 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -644,6 +644,40 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment): self.d.configure('neutron-api', set_default) u.log.debug('OK') + def test_500_enable_vlan_trunking(self): + """Check VLAN trunking""" + if self._get_openstack_release() >= self.xenial_newton: + u.log.debug('Checking VLAN trunking setting in neutron-api ' + 'neutron-openvswitch relation data...') + unit = self.neutron_api_sentry + relation = ['neutron-plugin-api', + 'neutron-openvswitch:neutron-plugin-api'] + + set_default = {'enable-vlan-trunking': 'False'} + set_alternate = {'enable-vlan-trunking': 'True'} + self.d.configure('neutron-api', set_alternate) + + relation_data = unit.relation(relation[0], relation[1]) + if relation_data['enable-vlan-trunking'] != 'True': + message = ("enable-vlan-trunking setting not set properly on " + "neutron-plugin-api relation") + amulet.raise_status(amulet.FAIL, msg=message) + + vlan_trunking_plugin = 'trunk' + config = u._get_config(unit, '/etc/neutron/neutron.conf') + service_plugins = config.get( + 'DEFAULT', + 'service_plugins').split(',') + if vlan_trunking_plugin not in service_plugins: + message = "{} not in service_plugins"\ + .format(vlan_trunking_plugin) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('Setting VLAN trunking back to {}'.format( + set_default['enable-vlan-trunking'])) + self.d.configure('neutron-api', set_default) + u.log.debug('OK') + def test_900_restart_on_config_change(self): """Verify that the specified services are restarted when the config is changed.""" diff --git a/unit_tests/test_neutron_api_context.py b/unit_tests/test_neutron_api_context.py index 0a122cba..2c2b5c3e 100644 --- a/unit_tests/test_neutron_api_context.py +++ b/unit_tests/test_neutron_api_context.py @@ -591,6 +591,7 @@ class NeutronCCContextTest(CharmTestCase): plugin.return_value = None self.test_config.set('enable-l3ha', True) self.test_config.set('enable-qos', False) + self.test_config.set('enable-vlan-trunking', False) self.test_config.set('overlay-network-type', 'gre') self.test_config.set('neutron-plugin', 'ovs') self.test_config.set('l2-population', False) @@ -788,6 +789,51 @@ class NeutronCCContextTest(CharmTestCase): for key in expect.keys(): self.assertEqual(napi_ctxt[key], expect[key]) + @patch.object(context.NeutronCCContext, 'network_manager') + @patch.object(context.NeutronCCContext, 'plugin') + @patch('builtins.__import__') + def test_neutroncc_context_vlan_trunking(self, _import, plugin, nm): + plugin.return_value = None + self.os_release.return_value = 'newton' + self.test_config.set('neutron-plugin', 'ovs') + self.test_config.set('enable-vlan-trunking', True) + napi_ctxt = context.NeutronCCContext()() + expected_service_plugins = ('router,firewall,vpnaas,metering,' + 'neutron_lbaas.services.loadbalancer.' + 'plugin.LoadBalancerPluginv2,trunk') + self.assertEqual(napi_ctxt['service_plugins'], + expected_service_plugins) + + @patch.object(context.NeutronCCContext, 'network_manager') + @patch.object(context.NeutronCCContext, 'plugin') + @patch('builtins.__import__') + def test_neutroncc_context_vlan_trunking_invalid_plugin(self, _import, + plugin, nm): + plugin.return_value = None + self.os_release.return_value = 'newton' + self.test_config.set('neutron-plugin', 'Calico') + self.test_config.set('enable-vlan-trunking', True) + napi_ctxt = context.NeutronCCContext()() + expected_service_plugins = ('router,firewall,vpnaas,metering,' + 'neutron_lbaas.services.loadbalancer.' + 'plugin.LoadBalancerPluginv2') + self.assertEqual(napi_ctxt['service_plugins'], + expected_service_plugins) + + @patch.object(context.NeutronCCContext, 'network_manager') + @patch.object(context.NeutronCCContext, 'plugin') + @patch('builtins.__import__') + def test_neutroncc_context_vlan_trunking_invalid_release(self, _import, + plugin, nm): + plugin.return_value = None + self.os_release.return_value = 'mitaka' + self.test_config.set('neutron-plugin', 'ovs') + self.test_config.set('enable-vlan-trunking', True) + napi_ctxt = context.NeutronCCContext()() + expected_service_plugins = ('router,firewall,lbaas,vpnaas,metering') + self.assertEqual(napi_ctxt['service_plugins'], + expected_service_plugins) + @patch.object(context.NeutronCCContext, 'network_manager') @patch.object(context.NeutronCCContext, 'plugin') @patch('builtins.__import__') @@ -795,6 +841,7 @@ class NeutronCCContextTest(CharmTestCase): plugin.return_value = None self.test_config.set('enable-qos', False) self.test_config.set('enable-ml2-port-security', False) + self.test_config.set('enable-vlan-trunking', False) # icehouse self.os_release.return_value = 'icehouse' service_plugins = ( diff --git a/unit_tests/test_neutron_api_hooks.py b/unit_tests/test_neutron_api_hooks.py index 0db48cc9..5b48b052 100644 --- a/unit_tests/test_neutron_api_hooks.py +++ b/unit_tests/test_neutron_api_hooks.py @@ -68,6 +68,7 @@ TO_PATCH = [ 'is_clustered', 'is_elected_leader', 'is_qos_requested_and_valid', + 'is_vlan_trunking_requested_and_valid', 'log', 'migrate_neutron_database', 'neutron_ready', @@ -469,6 +470,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'enable-dvr': False, 'enable-l3ha': False, 'enable-qos': False, + 'enable-vlan-trunking': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -488,6 +490,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-api-ready': 'no', } self.is_qos_requested_and_valid.return_value = False + self.is_vlan_trunking_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = False self.get_l2population.return_value = False @@ -508,6 +511,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'enable-dvr': True, 'enable-l3ha': False, 'enable-qos': False, + 'enable-vlan-trunking': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -527,6 +531,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-api-ready': 'no', } self.is_qos_requested_and_valid.return_value = False + self.is_vlan_trunking_requested_and_valid.return_value = False self.get_dvr.return_value = True self.get_l3ha.return_value = False self.get_l2population.return_value = True @@ -547,6 +552,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'enable-dvr': False, 'enable-l3ha': True, 'enable-qos': False, + 'enable-vlan-trunking': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -566,6 +572,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-api-ready': 'no', } self.is_qos_requested_and_valid.return_value = False + self.is_vlan_trunking_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = True self.get_l2population.return_value = False @@ -590,6 +597,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'report-interval': 30, 'l2-population': False, 'enable-qos': False, + 'enable-vlan-trunking': False, 'overlay-network-type': 'vxlan', 'network-device-mtu': 1500, 'enable-l3ha': True, @@ -607,6 +615,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-api-ready': 'no', } self.is_qos_requested_and_valid.return_value = False + self.is_vlan_trunking_requested_and_valid.return_value = False self.get_dvr.return_value = True self.get_l3ha.return_value = True self.get_l2population.return_value = False @@ -627,6 +636,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'enable-dvr': False, 'enable-l3ha': False, 'enable-qos': False, + 'enable-vlan-trunking': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -647,6 +657,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'dns-domain': 'openstack.example.' } self.is_qos_requested_and_valid.return_value = False + self.is_vlan_trunking_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = False self.get_l2population.return_value = False