[gnuoy,r=james-page] Add support for Neutron DVR and Router HA

This commit is contained in:
James Page 2015-03-31 08:53:39 +01:00
commit f5baf1572b
8 changed files with 423 additions and 4 deletions

View File

@ -285,6 +285,28 @@ options:
juju-myservice-0 juju-myservice-0
If you're running multiple environments with the same services in them If you're running multiple environments with the same services in them
this allows you to differentiate between them. this allows you to differentiate between them.
enable-dvr:
default: False
type: boolean
description: |
Enable Distributed Virtual Routing (juno and above).
enable-l3ha:
default: False
type: boolean
description: |
Enable L3 HA (juno and above).
max-l3-agents-per-router:
default: 2
type: int
description: |
Maximum number of l3 agents to host a router. Only used when enable-l3ha
is True
min-l3-agents-per-router:
default: 2
type: int
description: |
Minimum number of l3 agents to host a router. Only used when enable-l3ha
is True
nagios_servicegroups: nagios_servicegroups:
default: "" default: ""
type: string type: string

View File

@ -3,12 +3,16 @@ from charmhelpers.core.hookenv import (
relation_ids, relation_ids,
related_units, related_units,
relation_get, relation_get,
log,
) )
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_api_port, determine_api_port,
determine_apache_port, determine_apache_port,
) )
from charmhelpers.contrib.openstack.utils import (
os_release,
)
def get_l2population(): def get_l2population():
@ -23,6 +27,43 @@ def get_overlay_network_type():
return overlay_net return overlay_net
def get_l3ha():
if config('enable-l3ha'):
if os_release('neutron-server') < 'juno':
log('Disabling L3 HA, enable-l3ha is not valid before Juno')
return False
if config('overlay-network-type') not in ['vlan', 'gre', 'vxlan']:
log('Disabling L3 HA, enable-l3ha requires the use of the vxlan, '
'vlan or gre overlay network')
return False
if get_l2population():
log('Disabling L3 HA, l2-population must be disabled with L3 HA')
return False
return True
else:
return False
def get_dvr():
if config('enable-dvr'):
if os_release('neutron-server') < 'juno':
log('Disabling DVR, enable-dvr is not valid before Juno')
return False
if config('overlay-network-type') != 'vxlan':
log('Disabling DVR, enable-dvr requires the use of the vxlan '
'overlay network')
return False
if get_l3ha():
log('Disabling DVR, enable-l3ha must be disabled with dvr')
return False
if not get_l2population():
log('Disabling DVR, l2-population must be enabled to use dvr')
return False
return True
else:
return False
class ApacheSSLContext(context.ApacheSSLContext): class ApacheSSLContext(context.ApacheSSLContext):
interfaces = ['https'] interfaces = ['https']
@ -69,6 +110,14 @@ class NeutronCCContext(context.NeutronContext):
def neutron_overlay_network_type(self): def neutron_overlay_network_type(self):
return get_overlay_network_type() return get_overlay_network_type()
@property
def neutron_dvr(self):
return get_dvr()
@property
def neutron_l3ha(self):
return get_l3ha()
# Do not need the plugin agent installed on the api server # Do not need the plugin agent installed on the api server
def _ensure_packages(self): def _ensure_packages(self):
pass pass
@ -91,6 +140,13 @@ class NeutronCCContext(context.NeutronContext):
ctxt['nsx_controllers_list'] = \ ctxt['nsx_controllers_list'] = \
config('nsx-controllers').split() config('nsx-controllers').split()
ctxt['l2_population'] = self.neutron_l2_population ctxt['l2_population'] = self.neutron_l2_population
ctxt['enable_dvr'] = self.neutron_dvr
ctxt['l3_ha'] = self.neutron_l3ha
if self.neutron_l3ha:
ctxt['max_l3_agents_per_router'] = \
config('max-l3-agents-per-router')
ctxt['min_l3_agents_per_router'] = \
config('min-l3-agents-per-router')
ctxt['overlay_network_type'] = self.neutron_overlay_network_type ctxt['overlay_network_type'] = self.neutron_overlay_network_type
ctxt['external_network'] = config('neutron-external-network') ctxt['external_network'] = config('neutron-external-network')
ctxt['verbose'] = config('verbose') ctxt['verbose'] = config('verbose')

View File

@ -2,7 +2,6 @@
import sys import sys
import uuid import uuid
from subprocess import check_call from subprocess import check_call
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
Hooks, Hooks,
@ -40,12 +39,16 @@ from neutron_api_utils import (
determine_packages, determine_packages,
determine_ports, determine_ports,
do_openstack_upgrade, do_openstack_upgrade,
dvr_router_present,
l3ha_router_present,
register_configs, register_configs,
restart_map, restart_map,
services, services,
setup_ipv6 setup_ipv6
) )
from neutron_api_context import ( from neutron_api_context import (
get_dvr,
get_l3ha,
get_l2population, get_l2population,
get_overlay_network_type, get_overlay_network_type,
) )
@ -110,6 +113,16 @@ def install():
@hooks.hook('config-changed') @hooks.hook('config-changed')
@restart_on_change(restart_map(), stopstart=True) @restart_on_change(restart_map(), stopstart=True)
def config_changed(): def config_changed():
if l3ha_router_present() and not get_l3ha():
e = ('Cannot disable Router HA while ha enabled routers exist. Please'
' remove any ha routers')
log(e, level=ERROR)
raise Exception(e)
if dvr_router_present() and not get_dvr():
e = ('Cannot disable dvr while dvr enabled routers exist. Please'
' remove any distributed routers')
log(e, level=ERROR)
raise Exception(e)
apt_install(filter_installed_packages( apt_install(filter_installed_packages(
determine_packages(config('openstack-origin'))), determine_packages(config('openstack-origin'))),
fatal=True) fatal=True)
@ -278,6 +291,8 @@ def neutron_plugin_api_relation_joined(rid=None):
relation_data = { relation_data = {
'neutron-security-groups': config('neutron-security-groups'), 'neutron-security-groups': config('neutron-security-groups'),
'l2-population': get_l2population(), 'l2-population': get_l2population(),
'enable-dvr': get_dvr(),
'enable-l3ha': get_l3ha(),
'overlay-network-type': get_overlay_network_type(), 'overlay-network-type': get_overlay_network_type(),
} }

View File

@ -1,5 +1,6 @@
from collections import OrderedDict from collections import OrderedDict
from copy import deepcopy from copy import deepcopy
from functools import partial
import os import os
from base64 import b64encode from base64 import b64encode
from charmhelpers.contrib.openstack import context, templating from charmhelpers.contrib.openstack import context, templating
@ -244,3 +245,28 @@ def setup_ipv6():
' main') ' main')
apt_update() apt_update()
apt_install('haproxy/trusty-backports', fatal=True) apt_install('haproxy/trusty-backports', fatal=True)
def router_feature_present(feature):
''' Check For dvr enabled routers '''
env = neutron_api_context.IdentityServiceContext()()
if not env:
log('Unable to check resources at this time')
return
auth_url = '%(auth_protocol)s://%(auth_host)s:%(auth_port)s/v2.0' % env
# Late import to avoid install hook failures when pkg hasnt been installed
from neutronclient.v2_0 import client
neutron_client = client.Client(username=env['admin_user'],
password=env['admin_password'],
tenant_name=env['admin_tenant_name'],
auth_url=auth_url,
region_name=env['region'])
for router in neutron_client.list_routers()['routers']:
if router.get(feature, False):
return True
return False
l3ha_router_present = partial(router_feature_present, feature='ha')
dvr_router_present = partial(router_feature_present, feature='distributed')

View File

@ -0,0 +1,85 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
## Restart trigger {{ restart_trigger }}
###############################################################################
[DEFAULT]
verbose = {{ verbose }}
debug = {{ debug }}
use_syslog = {{ use_syslog }}
state_path = /var/lib/neutron
lock_path = $state_path/lock
bind_host = {{ bind_host }}
auth_strategy = keystone
notification_driver = neutron.openstack.common.notifier.rpc_notifier
api_workers = {{ workers }}
rpc_workers = {{ workers }}
router_distributed = {{ enable_dvr }}
l3_ha = {{ l3_ha }}
{% if l3_ha -%}
max_l3_agents_per_router = {{ max_l3_agents_per_router }}
min_l3_agents_per_router = {{ min_l3_agents_per_router }}
{% endif -%}
{% if neutron_bind_port -%}
bind_port = {{ neutron_bind_port }}
{% else -%}
bind_port = 9696
{% endif -%}
{% if core_plugin -%}
core_plugin = {{ core_plugin }}
{% 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
{% endif -%}
{% endif -%}
{% if neutron_security_groups -%}
allow_overlapping_ips = True
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%}
{% include "parts/rabbitmq" %}
notify_nova_on_port_status_changes = True
notify_nova_on_port_data_changes = True
nova_url = {{ nova_url }}
nova_region_name = {{ region }}
{% if auth_host -%}
nova_admin_username = {{ admin_user }}
nova_admin_tenant_id = {{ admin_tenant_id }}
nova_admin_password = {{ admin_password }}
nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
[quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver
{% if neutron_security_groups -%}
quota_items = network,subnet,port,security_group,security_group_rule
{% endif -%}
[agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
[keystone_authtoken]
signing_dir = /var/lib/neutron/keystone-signing
{% if service_host -%}
service_protocol = {{ service_protocol }}
service_host = {{ service_host }}
service_port = {{ service_port }}
auth_host = {{ auth_host }}
auth_port = {{ auth_port }}
auth_protocol = {{ auth_protocol }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% endif -%}
{% include "parts/section-database" %}
[service_providers]
service_provider=LOADBALANCER:Haproxy:neutron.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
service_provider=VPN:openswan:neutron.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default
service_provider=FIREWALL:Iptables:neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver:default

View File

@ -14,6 +14,14 @@ notification_driver = neutron.openstack.common.notifier.rpc_notifier
api_workers = {{ workers }} api_workers = {{ workers }}
rpc_workers = {{ workers }} rpc_workers = {{ workers }}
router_distributed = {{ enable_dvr }}
l3_ha = {{ l3_ha }}
{% if l3_ha -%}
max_l3_agents_per_router = {{ max_l3_agents_per_router }}
min_l3_agents_per_router = {{ min_l3_agents_per_router }}
{% endif -%}
{% if neutron_bind_port -%} {% if neutron_bind_port -%}
bind_port = {{ neutron_bind_port }} bind_port = {{ neutron_bind_port }}
{% else -%} {% else -%}

View File

@ -3,15 +3,131 @@ from mock import patch
import neutron_api_context as context import neutron_api_context as context
import charmhelpers import charmhelpers
TO_PATCH = [ TO_PATCH = [
'config',
'determine_api_port',
'determine_apache_port',
'log',
'os_release',
'relation_get', 'relation_get',
'relation_ids', 'relation_ids',
'related_units', 'related_units',
'config',
'determine_api_port',
'determine_apache_port'
] ]
class GeneralTests(CharmTestCase):
def setUp(self):
super(GeneralTests, self).setUp(context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get
def test_l2population(self):
self.test_config.set('l2-population', True)
self.test_config.set('neutron-plugin', 'ovs')
self.assertEquals(context.get_l2population(), True)
def test_l2population_nonovs(self):
self.test_config.set('l2-population', True)
self.test_config.set('neutron-plugin', 'nsx')
self.assertEquals(context.get_l2population(), False)
def test_get_overlay_network_type(self):
self.test_config.set('overlay-network-type', 'gre')
self.assertEquals(context.get_overlay_network_type(), 'gre')
def test_get_overlay_network_type_unsupported(self):
self.test_config.set('overlay-network-type', 'tokenring')
with self.assertRaises(Exception) as _exceptctxt:
context.get_overlay_network_type()
self.assertEqual(_exceptctxt.exception.message,
'Unsupported overlay-network-type')
def test_get_l3ha(self):
self.test_config.set('enable-l3ha', True)
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.assertEquals(context.get_l3ha(), True)
def test_get_l3ha_prejuno(self):
self.test_config.set('enable-l3ha', True)
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 = 'icehouse'
self.assertEquals(context.get_l3ha(), False)
def test_get_l3ha_l2pop(self):
self.test_config.set('enable-l3ha', True)
self.test_config.set('overlay-network-type', 'gre')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', True)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_l3ha(), False)
def test_get_l3ha_badoverlay(self):
self.test_config.set('enable-l3ha', True)
self.test_config.set('overlay-network-type', 'tokenring')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', False)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_l3ha(), False)
def test_get_dvr(self):
self.test_config.set('enable-dvr', True)
self.test_config.set('enable-l3ha', False)
self.test_config.set('overlay-network-type', 'vxlan')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', True)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_dvr(), True)
def test_get_dvr_explicit_off(self):
self.test_config.set('enable-dvr', False)
self.test_config.set('enable-l3ha', False)
self.test_config.set('overlay-network-type', 'vxlan')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', True)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_dvr(), False)
def test_get_dvr_prejuno(self):
self.test_config.set('enable-dvr', True)
self.test_config.set('enable-l3ha', False)
self.test_config.set('overlay-network-type', 'vxlan')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', True)
self.os_release.return_value = 'icehouse'
self.assertEquals(context.get_dvr(), False)
def test_get_dvr_gre(self):
self.test_config.set('enable-dvr', True)
self.test_config.set('enable-l3ha', False)
self.test_config.set('overlay-network-type', 'gre')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', True)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_dvr(), False)
def test_get_dvr_l3ha_on(self):
self.test_config.set('enable-dvr', True)
self.test_config.set('enable-l3ha', True)
self.test_config.set('overlay-network-type', 'vxlan')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', False)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_dvr(), False)
def test_get_dvr_l2pop(self):
self.test_config.set('enable-dvr', True)
self.test_config.set('enable-l3ha', False)
self.test_config.set('overlay-network-type', 'vxlan')
self.test_config.set('neutron-plugin', 'ovs')
self.test_config.set('l2-population', False)
self.os_release.return_value = 'juno'
self.assertEquals(context.get_dvr(), False)
class IdentityServiceContext(CharmTestCase): class IdentityServiceContext(CharmTestCase):
def setUp(self): def setUp(self):
@ -152,6 +268,8 @@ class NeutronCCContextTest(CharmTestCase):
plugin.return_value = None plugin.return_value = None
ctxt_data = { ctxt_data = {
'debug': True, 'debug': True,
'enable_dvr': False,
'l3_ha': False,
'external_network': 'bob', 'external_network': 'bob',
'neutron_bind_port': self.api_port, 'neutron_bind_port': self.api_port,
'verbose': True, 'verbose': True,
@ -181,6 +299,8 @@ class NeutronCCContextTest(CharmTestCase):
self.test_config.set('overlay-network-type', 'vxlan') self.test_config.set('overlay-network-type', 'vxlan')
ctxt_data = { ctxt_data = {
'debug': True, 'debug': True,
'enable_dvr': False,
'l3_ha': False,
'external_network': 'bob', 'external_network': 'bob',
'neutron_bind_port': self.api_port, 'neutron_bind_port': self.api_port,
'verbose': True, 'verbose': True,
@ -202,6 +322,43 @@ class NeutronCCContextTest(CharmTestCase):
with patch.object(napi_ctxt, '_ensure_packages'): with patch.object(napi_ctxt, '_ensure_packages'):
self.assertEquals(ctxt_data, napi_ctxt()) self.assertEquals(ctxt_data, napi_ctxt())
@patch.object(context.NeutronCCContext, 'network_manager')
@patch.object(context.NeutronCCContext, 'plugin')
@patch('__builtin__.__import__')
def test_neutroncc_context_l3ha(self, _import, plugin, nm):
plugin.return_value = None
self.test_config.set('enable-l3ha', True)
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'
ctxt_data = {
'debug': True,
'enable_dvr': False,
'l3_ha': True,
'external_network': 'bob',
'neutron_bind_port': self.api_port,
'verbose': True,
'l2_population': False,
'overlay_network_type': 'gre',
'max_l3_agents_per_router': 2,
'min_l3_agents_per_router': 2,
'quota_floatingip': 50,
'quota_health_monitors': -1,
'quota_member': -1,
'quota_network': 10,
'quota_pool': 10,
'quota_port': 50,
'quota_router': 10,
'quota_security_group': 10,
'quota_security_group_rule': 100,
'quota_subnet': 10,
'quota_vip': 10,
}
napi_ctxt = context.NeutronCCContext()
with patch.object(napi_ctxt, '_ensure_packages'):
self.assertEquals(ctxt_data, napi_ctxt())
@patch.object(context.NeutronCCContext, 'network_manager') @patch.object(context.NeutronCCContext, 'network_manager')
@patch.object(context.NeutronCCContext, 'plugin') @patch.object(context.NeutronCCContext, 'plugin')
@patch('__builtin__.__import__') @patch('__builtin__.__import__')

View File

@ -32,8 +32,12 @@ TO_PATCH = [
'determine_packages', 'determine_packages',
'determine_ports', 'determine_ports',
'do_openstack_upgrade', 'do_openstack_upgrade',
'dvr_router_present',
'l3ha_router_present',
'execd_preinstall', 'execd_preinstall',
'filter_installed_packages', 'filter_installed_packages',
'get_dvr',
'get_l3ha',
'get_l2population', 'get_l2population',
'get_overlay_network_type', 'get_overlay_network_type',
'is_relation_made', 'is_relation_made',
@ -93,6 +97,8 @@ class NeutronAPIHooksTests(CharmTestCase):
@patch.object(hooks, 'configure_https') @patch.object(hooks, 'configure_https')
def test_config_changed(self, conf_https): def test_config_changed(self, conf_https):
self.openstack_upgrade_available.return_value = True self.openstack_upgrade_available.return_value = True
self.dvr_router_present.return_value = False
self.l3ha_router_present.return_value = False
self.relation_ids.side_effect = self._fake_relids self.relation_ids.side_effect = self._fake_relids
_n_api_rel_joined = self.patch('neutron_api_relation_joined') _n_api_rel_joined = self.patch('neutron_api_relation_joined')
_n_plugin_api_rel_joined =\ _n_plugin_api_rel_joined =\
@ -274,9 +280,49 @@ class NeutronAPIHooksTests(CharmTestCase):
def test_neutron_plugin_api_relation_joined_nol2(self): def test_neutron_plugin_api_relation_joined_nol2(self):
_relation_data = { _relation_data = {
'neutron-security-groups': False, 'neutron-security-groups': False,
'enable-dvr': False,
'enable-l3ha': False,
'l2-population': False, 'l2-population': False,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
} }
self.get_dvr.return_value = False
self.get_l3ha.return_value = False
self.get_l2population.return_value = False
self.get_overlay_network_type.return_value = 'vxlan'
self._call_hook('neutron-plugin-api-relation-joined')
self.relation_set.assert_called_with(
relation_id=None,
**_relation_data
)
def test_neutron_plugin_api_relation_joined_dvr(self):
_relation_data = {
'neutron-security-groups': False,
'enable-dvr': True,
'enable-l3ha': False,
'l2-population': True,
'overlay-network-type': 'vxlan',
}
self.get_dvr.return_value = True
self.get_l3ha.return_value = False
self.get_l2population.return_value = True
self.get_overlay_network_type.return_value = 'vxlan'
self._call_hook('neutron-plugin-api-relation-joined')
self.relation_set.assert_called_with(
relation_id=None,
**_relation_data
)
def test_neutron_plugin_api_relation_joined_l3ha(self):
_relation_data = {
'neutron-security-groups': False,
'enable-dvr': False,
'enable-l3ha': True,
'l2-population': False,
'overlay-network-type': 'vxlan',
}
self.get_dvr.return_value = False
self.get_l3ha.return_value = True
self.get_l2population.return_value = False self.get_l2population.return_value = False
self.get_overlay_network_type.return_value = 'vxlan' self.get_overlay_network_type.return_value = 'vxlan'
self._call_hook('neutron-plugin-api-relation-joined') self._call_hook('neutron-plugin-api-relation-joined')
@ -292,7 +338,11 @@ class NeutronAPIHooksTests(CharmTestCase):
'l2-population': False, 'l2-population': False,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
'network-device-mtu': 1500, 'network-device-mtu': 1500,
'enable-l3ha': True,
'enable-dvr': True,
} }
self.get_dvr.return_value = True
self.get_l3ha.return_value = True
self.get_l2population.return_value = False self.get_l2population.return_value = False
self.get_overlay_network_type.return_value = 'vxlan' self.get_overlay_network_type.return_value = 'vxlan'
self._call_hook('neutron-plugin-api-relation-joined') self._call_hook('neutron-plugin-api-relation-joined')