[gnuoy, r=jamespage] Add support for neutron-plugin-api-subordinate relation

This commit is contained in:
Liam Young 2015-08-12 11:40:58 +01:00
commit 421056efa5
13 changed files with 306 additions and 23 deletions

View File

@ -368,3 +368,9 @@ options:
description: | description: |
A comma-separated list of nagios servicegroups. A comma-separated list of nagios servicegroups.
If left empty, the nagios_context will be used as the servicegroup If left empty, the nagios_context will be used as the servicegroup
manage-neutron-plugin-legacy-mode:
type: boolean
default: True
description: |
If True neutron-server will install neutron packages for the plugin
configured.

View File

@ -0,0 +1 @@
neutron_api_hooks.py

View File

@ -0,0 +1 @@
neutron_api_hooks.py

View File

@ -0,0 +1 @@
neutron_api_hooks.py

View File

@ -234,3 +234,63 @@ class HAProxyContext(context.HAProxyContext):
# for haproxy.conf # for haproxy.conf
ctxt['service_ports'] = port_mapping ctxt['service_ports'] = port_mapping
return ctxt return ctxt
class NeutronApiSDNContext(context.SubordinateConfigContext):
interfaces = 'neutron-plugin-api-subordinate'
def __init__(self):
super(NeutronApiSDNContext, self).__init__(
interface='neutron-plugin-api-subordinate',
service='neutron-api',
config_file='/etc/neutron/neutron.conf')
def __call__(self):
ctxt = super(NeutronApiSDNContext, self).__call__()
defaults = {
'core-plugin': {
'templ_key': 'core_plugin',
'value': 'neutron.plugins.ml2.plugin.Ml2Plugin',
},
'neutron-plugin-config': {
'templ_key': 'neutron_plugin_config',
'value': '/etc/neutron/plugins/ml2/ml2_conf.ini',
},
'service-plugins': {
'templ_key': 'service_plugins',
'value': 'router,firewall,lbaas,vpnaas,metering',
},
'restart-trigger': {
'templ_key': 'restart_trigger',
'value': '',
},
}
for rid in relation_ids('neutron-plugin-api-subordinate'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
plugin = rdata.get('neutron-plugin')
if not plugin:
continue
ctxt['neutron_plugin'] = plugin
for key in defaults.keys():
remote_value = rdata.get(key)
ctxt_key = defaults[key]['templ_key']
if remote_value:
ctxt[ctxt_key] = remote_value
else:
ctxt[ctxt_key] = defaults[key]['value']
return ctxt
return ctxt
class NeutronApiSDNConfigFileContext(context.OSContextGenerator):
interfaces = ['neutron-plugin-api-subordinate']
def __call__(self):
for rid in relation_ids('neutron-plugin-api-subordinate'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
neutron_server_plugin_conf = rdata.get('neutron-plugin-config')
if neutron_server_plugin_conf:
return {'config': neutron_server_plugin_conf}
return {'config': '/etc/neutron/plugins/ml2/ml2_conf.ini'}

View File

@ -479,7 +479,8 @@ def zeromq_configuration_relation_joined(relid=None):
users="neutron") users="neutron")
@hooks.hook('zeromq-configuration-relation-changed') @hooks.hook('zeromq-configuration-relation-changed',
'neutron-plugin-api-subordinate-relation-changed')
@restart_on_change(restart_map(), stopstart=True) @restart_on_change(restart_map(), stopstart=True)
def zeromq_configuration_relation_changed(): def zeromq_configuration_relation_changed():
CONFIGS.write_all() CONFIGS.write_all()

View File

@ -160,12 +160,17 @@ def api_port(service):
return API_PORTS[service] return API_PORTS[service]
def manage_plugin():
return config('manage-neutron-plugin-legacy-mode')
def determine_packages(source=None): def determine_packages(source=None):
# currently all packages match service names # currently all packages match service names
packages = [] + BASE_PACKAGES packages = [] + BASE_PACKAGES
for v in resource_map().values(): for v in resource_map().values():
packages.extend(v['services']) packages.extend(v['services'])
if manage_plugin():
pkgs = neutron_plugin_attribute(config('neutron-plugin'), pkgs = neutron_plugin_attribute(config('neutron-plugin'),
'server_packages', 'server_packages',
'neutron') 'neutron')
@ -211,8 +216,9 @@ def resource_map():
else: else:
resource_map.pop(APACHE_24_CONF) resource_map.pop(APACHE_24_CONF)
# add neutron plugin requirements. nova-c-c only needs the neutron-server if manage_plugin():
# associated with configs, not the plugin agent. # add neutron plugin requirements. nova-c-c only needs the
# neutron-server associated with configs, not the plugin agent.
plugin = config('neutron-plugin') plugin = config('neutron-plugin')
conf = neutron_plugin_attribute(plugin, 'config', 'neutron') conf = neutron_plugin_attribute(plugin, 'config', 'neutron')
ctxts = (neutron_plugin_attribute(plugin, 'contexts', 'neutron') ctxts = (neutron_plugin_attribute(plugin, 'contexts', 'neutron')
@ -229,6 +235,12 @@ def resource_map():
resource_map[conf]['contexts'].append( resource_map[conf]['contexts'].append(
context.PostgresqlDBContext(database=config('database'))) context.PostgresqlDBContext(database=config('database')))
else:
resource_map[NEUTRON_CONF]['contexts'].append(
neutron_api_context.NeutronApiSDNContext()
)
resource_map[NEUTRON_DEFAULT]['contexts'] = \
[neutron_api_context.NeutronApiSDNConfigFileContext()]
return resource_map return resource_map

View File

@ -37,6 +37,9 @@ requires:
zeromq-configuration: zeromq-configuration:
interface: zeromq-configuration interface: zeromq-configuration
scope: container scope: container
neutron-plugin-api-subordinate:
interface: neutron-plugin-api-subordinate
scope: container
peers: peers:
cluster: cluster:
interface: neutron-api-ha interface: neutron-api-ha

View File

@ -27,10 +27,14 @@ bind_port = 9696
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% if service_plugins -%}
service_plugins = {{ service_plugins }}
{% else -%}
{% if neutron_plugin in ['ovs', 'ml2'] -%} {% if neutron_plugin in ['ovs', 'ml2'] -%}
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 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 -%} {% endif -%}
{% endif -%}
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
@ -50,6 +54,12 @@ nova_admin_password = {{ admin_password }}
nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0 nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%} {% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
[quotas] [quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver quota_driver = neutron.db.quota_db.DbQuotaDriver
{% if neutron_security_groups -%} {% if neutron_security_groups -%}

View File

@ -54,6 +54,12 @@ nova_admin_password = {{ admin_password }}
nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0 nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%} {% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
[quotas] [quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver quota_driver = neutron.db.quota_db.DbQuotaDriver
{% if neutron_security_groups -%} {% if neutron_security_groups -%}

View File

@ -31,10 +31,14 @@ bind_port = 9696
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% if service_plugins -%}
service_plugins = {{ service_plugins }}
{% else -%}
{% if neutron_plugin in ['ovs', 'ml2'] -%} {% if neutron_plugin in ['ovs', 'ml2'] -%}
service_plugins = router,firewall,lbaas,vpnaas,metering service_plugins = router,firewall,lbaas,vpnaas,metering
{% endif -%} {% endif -%}
{% endif -%} {% endif -%}
{% endif -%}
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
@ -52,6 +56,12 @@ nova_admin_password = {{ admin_password }}
nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0 nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%} {% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
{% include "section-zeromq" %} {% include "section-zeromq" %}
[quotas] [quotas]

View File

@ -1,3 +1,4 @@
import json
from test_utils import CharmTestCase from test_utils import CharmTestCase
from mock import patch from mock import patch
import neutron_api_context as context import neutron_api_context as context
@ -432,3 +433,135 @@ class NeutronCCContextTest(CharmTestCase):
} }
for key in expect.iterkeys(): for key in expect.iterkeys():
self.assertEquals(napi_ctxt[key], expect[key]) self.assertEquals(napi_ctxt[key], expect[key])
class NeutronApiSDNContextTest(CharmTestCase):
def setUp(self):
super(NeutronApiSDNContextTest, self).setUp(context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
def tearDown(self):
super(NeutronApiSDNContextTest, self).tearDown()
def test_init(self):
napisdn_ctxt = context.NeutronApiSDNContext()
self.assertEquals(
napisdn_ctxt.interfaces,
['neutron-plugin-api-subordinate']
)
self.assertEquals(napisdn_ctxt.services, ['neutron-api'])
self.assertEquals(
napisdn_ctxt.config_file,
'/etc/neutron/neutron.conf'
)
@patch.object(charmhelpers.contrib.openstack.context, 'log')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
@patch.object(charmhelpers.contrib.openstack.context, 'related_units')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
def ctxt_check(self, rel_settings, expect, _rids, _runits, _rget, _log):
self.test_relation.set(rel_settings)
_runits.return_value = ['unit1']
_rids.return_value = ['rid2']
_rget.side_effect = self.test_relation.get
self.relation_ids.return_value = ['rid2']
self.related_units.return_value = ['unit1']
napisdn_ctxt = context.NeutronApiSDNContext()()
self.assertEquals(napisdn_ctxt, expect)
def test_defaults(self):
self.ctxt_check(
{'neutron-plugin': 'ovs'},
{
'core_plugin': 'neutron.plugins.ml2.plugin.Ml2Plugin',
'neutron_plugin_config': ('/etc/neutron/plugins/ml2/'
'ml2_conf.ini'),
'service_plugins': 'router,firewall,lbaas,vpnaas,metering',
'restart_trigger': '',
'neutron_plugin': 'ovs',
'sections': {},
}
)
def test_overrides(self):
self.ctxt_check(
{
'neutron-plugin': 'ovs',
'core-plugin': 'neutron.plugins.ml2.plugin.MidoPlumODL',
'neutron-plugin-config': '/etc/neutron/plugins/fl/flump.ini',
'service-plugins': 'router,unicorn,rainbows',
'restart-trigger': 'restartnow',
},
{
'core_plugin': 'neutron.plugins.ml2.plugin.MidoPlumODL',
'neutron_plugin_config': '/etc/neutron/plugins/fl/flump.ini',
'service_plugins': 'router,unicorn,rainbows',
'restart_trigger': 'restartnow',
'neutron_plugin': 'ovs',
'sections': {},
}
)
def test_subordinateconfig(self):
principle_config = {
"neutron-api": {
"/etc/neutron/neutron.conf": {
"sections": {
'DEFAULT': [
('neutronboost', True)
],
}
}
}
}
self.ctxt_check(
{
'neutron-plugin': 'ovs',
'subordinate_configuration': json.dumps(principle_config),
},
{
'core_plugin': 'neutron.plugins.ml2.plugin.Ml2Plugin',
'neutron_plugin_config': ('/etc/neutron/plugins/ml2/'
'ml2_conf.ini'),
'service_plugins': 'router,firewall,lbaas,vpnaas,metering',
'restart_trigger': '',
'neutron_plugin': 'ovs',
'sections': {u'DEFAULT': [[u'neutronboost', True]]},
}
)
def test_empty(self):
self.ctxt_check(
{},
{'sections': {}},
)
class NeutronApiSDNConfigFileContextTest(CharmTestCase):
def setUp(self):
super(NeutronApiSDNConfigFileContextTest, self).setUp(
context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
def tearDown(self):
super(NeutronApiSDNConfigFileContextTest, self).tearDown()
def test_configset(self):
self.test_relation.set({
'neutron-plugin-config': '/etc/neutron/superplugin.ini'
})
self.relation_ids.return_value = ['rid2']
self.related_units.return_value = ['unit1']
napisdn_ctxt = context.NeutronApiSDNConfigFileContext()()
self.assertEquals(napisdn_ctxt, {
'config': '/etc/neutron/superplugin.ini'
})
def test_default(self):
self.relation_ids.return_value = []
napisdn_ctxt = context.NeutronApiSDNConfigFileContext()()
self.assertEquals(napisdn_ctxt, {
'config': '/etc/neutron/plugins/ml2/ml2_conf.ini'
})

View File

@ -104,28 +104,57 @@ class TestNeutronAPIUtils(CharmTestCase):
expect.extend(nutils.KILO_PACKAGES) expect.extend(nutils.KILO_PACKAGES)
self.assertItemsEqual(pkg_list, expect) self.assertItemsEqual(pkg_list, expect)
@patch.object(nutils, 'git_install_requested')
def test_determine_packages_noplugin(self, git_requested):
git_requested.return_value = False
self.test_config.set('manage-neutron-plugin-legacy-mode', False)
pkg_list = nutils.determine_packages()
expect = deepcopy(nutils.BASE_PACKAGES)
expect.extend(['neutron-server'])
self.assertItemsEqual(pkg_list, expect)
def test_determine_ports(self): def test_determine_ports(self):
port_list = nutils.determine_ports() port_list = nutils.determine_ports()
self.assertItemsEqual(port_list, [9696]) self.assertItemsEqual(port_list, [9696])
@patch.object(nutils, 'manage_plugin')
@patch('os.path.exists') @patch('os.path.exists')
def test_resource_map(self, _path_exists): def test_resource_map(self, _path_exists, _manage_plugin):
_path_exists.return_value = False _path_exists.return_value = False
_manage_plugin.return_value = True
_map = nutils.resource_map() _map = nutils.resource_map()
confs = [nutils.NEUTRON_CONF, nutils.NEUTRON_DEFAULT, confs = [nutils.NEUTRON_CONF, nutils.NEUTRON_DEFAULT,
nutils.APACHE_CONF] nutils.APACHE_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs] [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
self.assertTrue(nutils.APACHE_24_CONF not in _map.keys()) self.assertTrue(nutils.APACHE_24_CONF not in _map.keys())
@patch.object(nutils, 'manage_plugin')
@patch('os.path.exists') @patch('os.path.exists')
def test_resource_map_apache24(self, _path_exists): def test_resource_map_apache24(self, _path_exists, _manage_plugin):
_path_exists.return_value = True _path_exists.return_value = True
_manage_plugin.return_value = True
_map = nutils.resource_map() _map = nutils.resource_map()
confs = [nutils.NEUTRON_CONF, nutils.NEUTRON_DEFAULT, confs = [nutils.NEUTRON_CONF, nutils.NEUTRON_DEFAULT,
nutils.APACHE_24_CONF] nutils.APACHE_24_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs] [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
self.assertTrue(nutils.APACHE_CONF not in _map.keys()) self.assertTrue(nutils.APACHE_CONF not in _map.keys())
@patch.object(nutils, 'manage_plugin')
@patch('os.path.exists')
def test_resource_map_noplugin(self, _path_exists, _manage_plugin):
_path_exists.return_value = True
_manage_plugin.return_value = False
_map = nutils.resource_map()
found_sdn_ctxt = False
found_sdnconfig_ctxt = False
for ctxt in _map[nutils.NEUTRON_CONF]['contexts']:
if isinstance(ctxt, ncontext.NeutronApiSDNContext):
found_sdn_ctxt = True
for ctxt in _map[nutils.NEUTRON_DEFAULT]['contexts']:
if isinstance(ctxt, ncontext.NeutronApiSDNConfigFileContext):
found_sdnconfig_ctxt = True
self.assertTrue(found_sdn_ctxt and found_sdnconfig_ctxt)
@patch('os.path.exists') @patch('os.path.exists')
def test_restart_map(self, mock_path_exists): def test_restart_map(self, mock_path_exists):
mock_path_exists.return_value = False mock_path_exists.return_value = False
@ -520,3 +549,13 @@ class TestNeutronAPIUtils(CharmTestCase):
'upgrade', 'upgrade',
'head'] 'head']
self.subprocess.check_output.assert_called_with(cmd) self.subprocess.check_output.assert_called_with(cmd)
def test_manage_plugin_true(self):
self.test_config.set('manage-neutron-plugin-legacy-mode', True)
manage = nutils.manage_plugin()
self.assertTrue(manage)
def test_manage_plugin_false(self):
self.test_config.set('manage-neutron-plugin-legacy-mode', False)
manage = nutils.manage_plugin()
self.assertFalse(manage)