diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 35e08d470e77..290c7aa69e9d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1173,6 +1173,8 @@ class ComputeManager(manager.Manager): # if the configuration is wrong. whitelist.Whitelist(CONF.pci.passthrough_whitelist) + nova.conf.neutron.register_dynamic_opts(CONF) + self.driver.init_host(host=self.host) context = nova.context.get_admin_context() instances = objects.InstanceList.get_by_host( diff --git a/nova/conf/neutron.py b/nova/conf/neutron.py index 48cadc77cea9..33fca355dd2b 100644 --- a/nova/conf/neutron.py +++ b/nova/conf/neutron.py @@ -76,6 +76,42 @@ Neutron for extensions. After this number of seconds the next time Nova needs to create a resource in Neutron it will requery Neutron for the extensions that it has loaded. Setting value to 0 will refresh the extensions with no wait. +"""), + cfg.ListOpt('physnets', + default=[], + help=""" +List of physnets present on this host. + +For each *physnet* listed, an additional section, +``[neutron_physnet_$PHYSNET]``, will be added to the configuration file. Each +section must be configured with a single configuration option, ``numa_nodes``, +which should be a list of node IDs for all NUMA nodes this physnet is +associated with. For example:: + + [neutron] + physnets = foo, bar + + [neutron_physnet_foo] + numa_nodes = 0 + + [neutron_physnet_bar] + numa_nodes = 0,1 + +Any *physnet* that is not listed using this option will be treated as having no +particular NUMA node affinity. + +Tunnelled networks (VXLAN, GRE, ...) cannot be accounted for in this way and +are instead configured using the ``[neutron_tunnel]`` group. For example:: + + [neutron_tunnel] + numa_nodes = 1 + +Related options: + +* ``[neutron_tunnel] numa_nodes`` can be used to configure NUMA affinity for + all tunneled networks +* ``[neutron_physnet_$PHYSNET] numa_nodes`` must be configured for each value + of ``$PHYSNET`` specified by this option """), ] @@ -118,6 +154,26 @@ def register_opts(conf): confutils.register_ksa_opts(conf, neutron_group, DEFAULT_SERVICE_TYPE) +def register_dynamic_opts(conf): + """Register dynamically-generated options and groups. + + This must be called by the service that wishes to use the options **after** + the initial configuration has been loaded. + """ + opt = cfg.ListOpt('numa_nodes', default=[], item_type=cfg.types.Integer()) + + # Register the '[neutron_tunnel] numa_nodes' opt, implicitly + # registering the '[neutron_tunnel]' group in the process. This could + # be done statically but is done to avoid this group appearing in + # nova.conf documentation while the other group does not. + conf.register_opt(opt, group='neutron_tunnel') + + # Register the '[neutron_physnet_$PHYSNET] numa_nodes' opts, implicitly + # registering the '[neutron_physnet_$PHYSNET]' groups in the process + for physnet in conf.neutron.physnets: + conf.register_opt(opt, group='neutron_physnet_%s' % physnet) + + def list_opts(): return { neutron_group: ( diff --git a/nova/tests/unit/conf/__init__.py b/nova/tests/unit/conf/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nova/tests/unit/conf/test_neutron.py b/nova/tests/unit/conf/test_neutron.py new file mode 100644 index 000000000000..b784486f9d46 --- /dev/null +++ b/nova/tests/unit/conf/test_neutron.py @@ -0,0 +1,33 @@ +# Copyright 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import nova.conf +from nova import test + + +CONF = nova.conf.CONF + + +class NeutronConfTestCase(test.NoDBTestCase): + + def test_register_dynamic_opts(self): + self.flags(physnets=['foo', 'bar', 'baz'], group='neutron') + + self.assertNotIn('neutron_physnet_foo', CONF) + self.assertNotIn('neutron_physnet_bar', CONF) + + nova.conf.neutron.register_dynamic_opts(CONF) + + self.assertIn('neutron_physnet_foo', CONF) + self.assertIn('neutron_physnet_bar', CONF)