diff --git a/.gitignore b/.gitignore index 1ba85a76..6405b24c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ tags .unit-state.db trusty/ xenial/ +.stestr diff --git a/config.yaml b/config.yaml index 0ff438c7..a0faad01 100755 --- a/config.yaml +++ b/config.yaml @@ -381,6 +381,11 @@ options: Seconds between nodes reporting state to server; should be less than agent_down_time, best if it is half or less than agent_down_time. Used by all neutron agents (includes l2 agents and other types of agents). + enable-qos: + type: boolean + default: False + description: | + Enable QoS (assuming the plug-in or ml2 mechanism driver can support it) # Quota config quota-security-group: type: int diff --git a/hooks/neutron_api_context.py b/hooks/neutron_api_context.py index f4e2beb8..282f76c7 100644 --- a/hooks/neutron_api_context.py +++ b/hooks/neutron_api_context.py @@ -47,6 +47,7 @@ TENANT_NET_TYPES = [VXLAN, GRE, VLAN, FLAT, LOCAL] EXTENSION_DRIVER_PORT_SECURITY = 'port_security' EXTENSION_DRIVER_DNS = 'dns' +EXTENSION_DRIVER_QOS = 'qos' ETC_NEUTRON = '/etc/neutron' @@ -201,6 +202,23 @@ def get_ml2_mechanism_drivers(): return ','.join(mechanism_drivers) +def is_qos_requested_and_valid(): + """Check whether QoS should be enabled by checking whether it has been + requested and, if it has, is it supported in the current configuration + """ + + if config('enable-qos'): + if CompareOpenStackReleases(os_release('neutron-server')) < 'mitaka': + msg = ("The enable-qos option is only supported on mitaka or " + "later") + log(msg, ERROR) + return False + else: + return True + else: + return False + + class ApacheSSLContext(context.ApacheSSLContext): interfaces = ['https'] @@ -391,6 +409,10 @@ class NeutronCCContext(context.NeutronContext): if dns_domain: extension_drivers.append(EXTENSION_DRIVER_DNS) ctxt['dns_domain'] = dns_domain + + if is_qos_requested_and_valid(): + extension_drivers.append(EXTENSION_DRIVER_QOS) + if extension_drivers: ctxt['extension_drivers'] = ','.join(extension_drivers) @@ -412,6 +434,47 @@ class NeutronCCContext(context.NeutronContext): ctxt['mechanism_drivers'] = get_ml2_mechanism_drivers() + if config('neutron-plugin') in ['ovs', 'ml2', 'Calico']: + ctxt['service_plugins'] = [] + service_plugins = { + 'icehouse': [ + ('neutron.services.l3_router.l3_router_plugin.' + 'L3RouterPlugin'), + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin', + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin', + 'neutron.services.vpn.plugin.VPNDriverPlugin', + ('neutron.services.metering.metering_plugin.' + 'MeteringPlugin')], + 'juno': [ + ('neutron.services.l3_router.l3_router_plugin.' + 'L3RouterPlugin'), + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin', + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin', + 'neutron.services.vpn.plugin.VPNDriverPlugin', + ('neutron.services.metering.metering_plugin.' + 'MeteringPlugin')], + 'kilo': ['router', 'firewall', 'lbaas', 'vpnaas', 'metering'], + 'liberty': ['router', 'firewall', 'lbaas', 'vpnaas', + 'metering'], + 'mitaka': ['router', 'firewall', 'lbaas', 'vpnaas', + 'metering'], + 'newton': ['router', 'firewall', 'vpnaas', 'metering', + ('neutron_lbaas.services.loadbalancer.plugin.' + 'LoadBalancerPluginv2')], + 'ocata': ['router', 'firewall', 'vpnaas', 'metering', + ('neutron_lbaas.services.loadbalancer.plugin.' + 'LoadBalancerPluginv2')], + 'pike': ['router', 'firewall', 'metering', + ('neutron_lbaas.services.loadbalancer.plugin.' + 'LoadBalancerPluginv2')], + } + ctxt['service_plugins'] = service_plugins.get( + release, service_plugins['pike']) + + if is_qos_requested_and_valid(): + ctxt['service_plugins'].append('qos') + ctxt['service_plugins'] = ','.join(ctxt['service_plugins']) + return ctxt diff --git a/hooks/neutron_api_hooks.py b/hooks/neutron_api_hooks.py index ff4377b3..fd5701cb 100755 --- a/hooks/neutron_api_hooks.py +++ b/hooks/neutron_api_hooks.py @@ -94,6 +94,7 @@ from neutron_api_context import ( get_l2population, get_overlay_network_type, IdentityServiceContext, + is_qos_requested_and_valid, EtcdContext, ) @@ -484,6 +485,7 @@ def neutron_plugin_api_relation_joined(rid=None): 'l2-population': get_l2population(), 'enable-dvr': get_dvr(), 'enable-l3ha': get_l3ha(), + 'enable-qos': is_qos_requested_and_valid(), 'overlay-network-type': get_overlay_network_type(), 'addr': unit_get('private-address'), 'polling-interval': config('polling-interval'), diff --git a/templates/icehouse/neutron.conf b/templates/icehouse/neutron.conf index f5260846..e7b79a07 100644 --- a/templates/icehouse/neutron.conf +++ b/templates/icehouse/neutron.conf @@ -29,10 +29,6 @@ bind_port = 9696 core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neutron.services.firewall.fwaas_plugin.FirewallPlugin,neutron.services.loadbalancer.plugin.LoadBalancerPlugin,neutron.services.vpn.plugin.VPNDriverPlugin,neutron.services.metering.metering_plugin.MeteringPlugin -{% endif -%} {% endif -%} {% endif -%} diff --git a/templates/juno/neutron.conf b/templates/juno/neutron.conf index aecd44ee..2ad2105f 100644 --- a/templates/juno/neutron.conf +++ b/templates/juno/neutron.conf @@ -31,8 +31,8 @@ bind_port = 9696 {% if core_plugin -%} core_plugin = {{ core_plugin }} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neutron.services.firewall.fwaas_plugin.FirewallPlugin,neutron.services.loadbalancer.plugin.LoadBalancerPlugin,neutron.services.vpn.plugin.VPNDriverPlugin,neutron.services.metering.metering_plugin.MeteringPlugin +{% if service_plugins -%} +service_plugins = {{ service_plugins }} {% endif -%} {% endif -%} diff --git a/templates/kilo/neutron.conf b/templates/kilo/neutron.conf index 6b51f5fb..1c052538 100644 --- a/templates/kilo/neutron.conf +++ b/templates/kilo/neutron.conf @@ -33,10 +33,6 @@ bind_port = 9696 core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = router,firewall,lbaas,vpnaas,metering -{% endif -%} {% endif -%} {% endif -%} diff --git a/templates/liberty/neutron.conf b/templates/liberty/neutron.conf index 621527c6..d0d52aed 100644 --- a/templates/liberty/neutron.conf +++ b/templates/liberty/neutron.conf @@ -38,14 +38,10 @@ api_extensions_path = {{ api_extensions_path }} core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = router,firewall,lbaas,vpnaas,metering {% elif neutron_plugin == 'midonet' -%} service_plugins = lbaas {% endif -%} {% endif -%} -{% endif -%} {% if neutron_security_groups -%} allow_overlapping_ips = True diff --git a/templates/mitaka/neutron.conf b/templates/mitaka/neutron.conf index 45f13371..54497206 100644 --- a/templates/mitaka/neutron.conf +++ b/templates/mitaka/neutron.conf @@ -36,10 +36,6 @@ bind_port = 9696 core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = router,firewall,lbaas,vpnaas,metering -{% endif -%} {% endif -%} {% endif -%} diff --git a/templates/newton/neutron.conf b/templates/newton/neutron.conf index 387cdcd2..3e509347 100644 --- a/templates/newton/neutron.conf +++ b/templates/newton/neutron.conf @@ -36,10 +36,6 @@ bind_port = 9696 core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = router,firewall,vpnaas,metering,neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2 -{% endif -%} {% endif -%} {% endif -%} diff --git a/templates/pike/neutron.conf b/templates/pike/neutron.conf index 5612bf4b..263414b6 100644 --- a/templates/pike/neutron.conf +++ b/templates/pike/neutron.conf @@ -32,10 +32,6 @@ bind_port = 9696 core_plugin = {{ core_plugin }} {% if service_plugins -%} service_plugins = {{ service_plugins }} -{% else -%} -{% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%} -service_plugins = router,firewall,metering,neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2 -{% endif -%} {% endif -%} {% endif -%} diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index d34991f5..85425fe1 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -660,6 +660,39 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment): message = "ml2 config error: {}".format(ret) amulet.raise_status(amulet.FAIL, msg=message) + def test_400_enable_qos(self): + """Check qos settings""" + if self._get_openstack_release() >= self.trusty_mitaka: + u.log.debug('Checking QoS 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-qos': 'False'} + set_alternate = {'enable-qos': 'True'} + self.d.configure('neutron-api', set_alternate) + + relation_data = unit.relation(relation[0], relation[1]) + if relation_data['enable-qos'] != 'True': + message = ("enable-qos setting not set properly on " + "neutron-plugin-api relation") + amulet.raise_status(amulet.FAIL, msg=message) + + qos_plugin = 'qos' + config = u._get_config(unit, '/etc/neutron/neutron.conf') + service_plugins = config.get( + 'DEFAULT', + 'service_plugins').split(',') + if qos_plugin not in service_plugins: + message = "{} not in service_plugins".format(qos_plugin) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('Setting QoS back to {}'.format( + set_default['enable-qos'])) + 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 1e33f9bf..e32a3809 100644 --- a/unit_tests/test_neutron_api_context.py +++ b/unit_tests/test_neutron_api_context.py @@ -442,9 +442,15 @@ class NeutronCCContextTest(CharmTestCase): 'vlan_ranges': 'physnet1:1000:2000', 'vni_ranges': '1001:2000', 'extension_drivers': 'port_security', + 'service_plugins': ( + 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,' + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin,' + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin,' + 'neutron.services.vpn.plugin.VPNDriverPlugin,' + 'neutron.services.metering.metering_plugin.MeteringPlugin'), } napi_ctxt = context.NeutronCCContext() - self.os_release.return_value = 'havana' + self.os_release.return_value = 'icehouse' with patch.object(napi_ctxt, '_ensure_packages'): self.assertEqual(ctxt_data, napi_ctxt()) @@ -528,9 +534,15 @@ class NeutronCCContextTest(CharmTestCase): 'vni_ranges': '1001:2000,3001:4000', 'network_providers': 'physnet2,physnet3', 'extension_drivers': 'port_security', + 'service_plugins': ( + 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,' + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin,' + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin,' + 'neutron.services.vpn.plugin.VPNDriverPlugin,' + 'neutron.services.metering.metering_plugin.MeteringPlugin'), } napi_ctxt = context.NeutronCCContext() - self.os_release.return_value = 'havana' + self.os_release.return_value = 'icehouse' with patch.object(napi_ctxt, '_ensure_packages'): self.assertEqual(ctxt_data, napi_ctxt()) @@ -540,10 +552,12 @@ class NeutronCCContextTest(CharmTestCase): def test_neutroncc_context_l3ha(self, _import, plugin, nm): plugin.return_value = None self.test_config.set('enable-l3ha', True) + self.test_config.set('enable-qos', False) self.test_config.set('overlay-network-type', 'gre') self.test_config.set('neutron-plugin', 'ovs') self.test_config.set('l2-population', False) self.os_release.return_value = 'juno' + self.maxDiff = None ctxt_data = { 'debug': True, 'enable_dvr': False, @@ -573,6 +587,12 @@ class NeutronCCContextTest(CharmTestCase): 'vlan_ranges': 'physnet1:1000:2000', 'vni_ranges': '1001:2000', 'extension_drivers': 'port_security', + 'service_plugins': ( + 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,' + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin,' + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin,' + 'neutron.services.vpn.plugin.VPNDriverPlugin,' + 'neutron.services.metering.metering_plugin.MeteringPlugin'), } napi_ctxt = context.NeutronCCContext() with patch.object(napi_ctxt, '_ensure_packages'): @@ -629,6 +649,7 @@ class NeutronCCContextTest(CharmTestCase): 'vlan_ranges': 'physnet1:1000:2000', 'vni_ranges': '1001:2000', 'extension_drivers': 'port_security', + 'service_plugins': 'router,firewall,lbaas,vpnaas,metering', } napi_ctxt = context.NeutronCCContext() self.os_release.return_value = 'kilo' @@ -712,6 +733,87 @@ class NeutronCCContextTest(CharmTestCase): for key in expect.iterkeys(): self.assertEqual(napi_ctxt[key], expect[key]) + @patch.object(context.NeutronCCContext, 'network_manager') + @patch.object(context.NeutronCCContext, 'plugin') + @patch('__builtin__.__import__') + def test_neutroncc_context_qos(self, _import, plugin, nm): + plugin.return_value = None + self.os_release.return_value = 'mitaka' + self.test_config.set('enable-qos', True) + self.test_config.set('enable-ml2-port-security', False) + napi_ctxt = context.NeutronCCContext()() + service_plugins = ('router,firewall,lbaas,vpnaas,metering,qos') + expect = { + 'extension_drivers': 'qos', + 'service_plugins': service_plugins, + } + for key in expect.iterkeys(): + self.assertEqual(napi_ctxt[key], expect[key]) + + @patch.object(context.NeutronCCContext, 'network_manager') + @patch.object(context.NeutronCCContext, 'plugin') + @patch('__builtin__.__import__') + def test_neutroncc_context_service_plugins(self, _import, plugin, nm): + plugin.return_value = None + self.test_config.set('enable-qos', False) + self.test_config.set('enable-ml2-port-security', False) + # icehouse + self.os_release.return_value = 'icehouse' + service_plugins = ( + 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,' + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin,' + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin,' + 'neutron.services.vpn.plugin.VPNDriverPlugin,' + 'neutron.services.metering.metering_plugin.MeteringPlugin') + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # juno + self.os_release.return_value = 'juno' + service_plugins = ( + 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,' + 'neutron.services.firewall.fwaas_plugin.FirewallPlugin,' + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin,' + 'neutron.services.vpn.plugin.VPNDriverPlugin,' + 'neutron.services.metering.metering_plugin.MeteringPlugin') + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # kilo + self.os_release.return_value = 'kilo' + service_plugins = 'router,firewall,lbaas,vpnaas,metering' + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # liberty + self.os_release.return_value = 'liberty' + service_plugins = 'router,firewall,lbaas,vpnaas,metering' + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # mitaka + self.os_release.return_value = 'mitaka' + service_plugins = 'router,firewall,lbaas,vpnaas,metering' + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # newton + self.os_release.return_value = 'newton' + service_plugins = ( + 'router,firewall,vpnaas,metering,' + 'neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2') + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # ocata + self.os_release.return_value = 'ocata' + service_plugins = ( + 'router,firewall,vpnaas,metering,' + 'neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2') + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + # pike + self.os_release.return_value = 'pike' + service_plugins = ( + 'router,firewall,metering,' + 'neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2') + self.assertEqual(context.NeutronCCContext()()['service_plugins'], + service_plugins) + class EtcdContextTest(CharmTestCase): diff --git a/unit_tests/test_neutron_api_hooks.py b/unit_tests/test_neutron_api_hooks.py index a8b61ed0..2d7ba02b 100644 --- a/unit_tests/test_neutron_api_hooks.py +++ b/unit_tests/test_neutron_api_hooks.py @@ -67,6 +67,7 @@ TO_PATCH = [ 'get_overlay_network_type', 'git_install', 'is_elected_leader', + 'is_qos_requested_and_valid', 'is_relation_made', 'log', 'migrate_neutron_database', @@ -602,6 +603,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-security-groups': False, 'enable-dvr': False, 'enable-l3ha': False, + 'enable-qos': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -620,6 +622,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'service_host': None, 'neutron-api-ready': 'no', } + self.is_qos_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = False self.get_l2population.return_value = False @@ -639,6 +642,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-security-groups': False, 'enable-dvr': True, 'enable-l3ha': False, + 'enable-qos': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -657,6 +661,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'service_host': None, 'neutron-api-ready': 'no', } + self.is_qos_requested_and_valid.return_value = False self.get_dvr.return_value = True self.get_l3ha.return_value = False self.get_l2population.return_value = True @@ -676,6 +681,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-security-groups': False, 'enable-dvr': False, 'enable-l3ha': True, + 'enable-qos': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -694,6 +700,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'service_host': None, 'neutron-api-ready': 'no', } + self.is_qos_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = True self.get_l2population.return_value = False @@ -717,6 +724,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'rpc-response-timeout': 60, 'report-interval': 30, 'l2-population': False, + 'enable-qos': False, 'overlay-network-type': 'vxlan', 'network-device-mtu': 1500, 'enable-l3ha': True, @@ -733,6 +741,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'service_host': None, 'neutron-api-ready': 'no', } + self.is_qos_requested_and_valid.return_value = False self.get_dvr.return_value = True self.get_l3ha.return_value = True self.get_l2population.return_value = False @@ -752,6 +761,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-security-groups': False, 'enable-dvr': False, 'enable-l3ha': False, + 'enable-qos': False, 'addr': '172.18.18.18', 'polling-interval': 2, 'rpc-response-timeout': 60, @@ -771,6 +781,7 @@ class NeutronAPIHooksTests(CharmTestCase): 'neutron-api-ready': 'no', 'dns-domain': 'openstack.example.' } + self.is_qos_requested_and_valid.return_value = False self.get_dvr.return_value = False self.get_l3ha.return_value = False self.get_l2population.return_value = False