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

This commit is contained in:
James Page 2015-04-01 13:53:02 +01:00
commit be6b39e892
14 changed files with 581 additions and 75 deletions

View File

@ -60,3 +60,12 @@ options:
. .
This network will be used for tenant network traffic in overlay This network will be used for tenant network traffic in overlay
networks. networks.
ext-port:
type: string
default:
description: |
A space-separated list of external ports to use for routing of instance
traffic to the external public network. Valid values are either MAC
addresses (in which case only MAC addresses for interfaces without an IP
address already assigned will be used), or interfaces (eth0)

View File

@ -320,14 +320,15 @@ def db_ssl(rdata, ctxt, ssl_dir):
class IdentityServiceContext(OSContextGenerator): class IdentityServiceContext(OSContextGenerator):
interfaces = ['identity-service']
def __init__(self, service=None, service_user=None): def __init__(self, service=None, service_user=None, rel_name='identity-service'):
self.service = service self.service = service
self.service_user = service_user self.service_user = service_user
self.rel_name = rel_name
self.interfaces = [self.rel_name]
def __call__(self): def __call__(self):
log('Generating template context for identity-service', level=DEBUG) log('Generating template context for ' + self.rel_name, level=DEBUG)
ctxt = {} ctxt = {}
if self.service and self.service_user: if self.service and self.service_user:
@ -341,7 +342,7 @@ class IdentityServiceContext(OSContextGenerator):
ctxt['signing_dir'] = cachedir ctxt['signing_dir'] = cachedir
for rid in relation_ids('identity-service'): for rid in relation_ids(self.rel_name):
for unit in related_units(rid): for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit) rdata = relation_get(rid=rid, unit=unit)
serv_host = rdata.get('service_host') serv_host = rdata.get('service_host')

View File

@ -1,25 +1,23 @@
import os
import uuid
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
config, config,
relation_get,
relation_ids,
related_units,
unit_get, unit_get,
) )
from charmhelpers.contrib.openstack.ip import resolve_address
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
from charmhelpers.core.host import (
service_running,
service_start,
service_restart,
)
from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port
from charmhelpers.contrib.openstack.utils import get_host_ip from charmhelpers.contrib.openstack.utils import get_host_ip
from charmhelpers.contrib.network.ip import get_address_in_network from charmhelpers.contrib.network.ip import get_address_in_network
from charmhelpers.contrib.openstack.context import ( from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
NeutronAPIContext, NeutronAPIContext,
DataPortContext,
) )
from charmhelpers.contrib.openstack.neutron import ( from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
parse_vlan_range_mappings, parse_vlan_range_mappings,
) )
OVS_BRIDGE = 'br-int'
class OVSPluginContext(context.NeutronContext): class OVSPluginContext(context.NeutronContext):
@ -40,24 +38,6 @@ class OVSPluginContext(context.NeutronContext):
neutron_api_settings = NeutronAPIContext()() neutron_api_settings = NeutronAPIContext()()
return neutron_api_settings['neutron_security_groups'] return neutron_api_settings['neutron_security_groups']
def _ensure_bridge(self):
if not service_running('openvswitch-switch'):
service_start('openvswitch-switch')
add_bridge(OVS_BRIDGE)
portmaps = DataPortContext()()
bridgemaps = parse_bridge_mappings(config('bridge-mappings'))
for provider, br in bridgemaps.iteritems():
add_bridge(br)
if not portmaps or br not in portmaps:
continue
add_bridge_port(br, portmaps[br], promisc=True)
service_restart('os-charm-phy-nic-mtu')
def ovs_ctxt(self): def ovs_ctxt(self):
# In addition to generating config context, ensure the OVS service # In addition to generating config context, ensure the OVS service
# is running and the OVS bridge exists. Also need to ensure # is running and the OVS bridge exists. Also need to ensure
@ -66,8 +46,6 @@ class OVSPluginContext(context.NeutronContext):
if not ovs_ctxt: if not ovs_ctxt:
return {} return {}
self._ensure_bridge()
conf = config() conf = config()
ovs_ctxt['local_ip'] = \ ovs_ctxt['local_ip'] = \
get_address_in_network(config('os-data-network'), get_address_in_network(config('os-data-network'),
@ -75,6 +53,7 @@ class OVSPluginContext(context.NeutronContext):
neutron_api_settings = NeutronAPIContext()() neutron_api_settings = NeutronAPIContext()()
ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups
ovs_ctxt['l2_population'] = neutron_api_settings['l2_population'] ovs_ctxt['l2_population'] = neutron_api_settings['l2_population']
ovs_ctxt['distributed_routing'] = neutron_api_settings['enable_dvr']
ovs_ctxt['overlay_network_type'] = \ ovs_ctxt['overlay_network_type'] = \
neutron_api_settings['overlay_network_type'] neutron_api_settings['overlay_network_type']
# TODO: We need to sort out the syslog and debug/verbose options as a # TODO: We need to sort out the syslog and debug/verbose options as a
@ -102,3 +81,62 @@ class OVSPluginContext(context.NeutronContext):
ovs_ctxt['vlan_ranges'] = vlan_ranges ovs_ctxt['vlan_ranges'] = vlan_ranges
return ovs_ctxt return ovs_ctxt
class L3AgentContext(OSContextGenerator):
def __call__(self):
neutron_api_settings = NeutronAPIContext()()
ctxt = {}
if neutron_api_settings['enable_dvr']:
ctxt['agent_mode'] = 'dvr'
else:
ctxt['agent_mode'] = 'legacy'
return ctxt
SHARED_SECRET = "/etc/neutron/secret.txt"
def get_shared_secret():
secret = None
if not os.path.exists(SHARED_SECRET):
secret = str(uuid.uuid4())
with open(SHARED_SECRET, 'w') as secret_file:
secret_file.write(secret)
else:
with open(SHARED_SECRET, 'r') as secret_file:
secret = secret_file.read().strip()
return secret
class DVRSharedSecretContext(OSContextGenerator):
def __call__(self):
if NeutronAPIContext()()['enable_dvr']:
ctxt = {
'shared_secret': get_shared_secret(),
'local_ip': resolve_address(),
}
else:
ctxt = {}
return ctxt
class APIIdentityServiceContext(context.IdentityServiceContext):
def __init__(self):
super(APIIdentityServiceContext,
self).__init__(rel_name='neutron-plugin-api')
def __call__(self):
ctxt = super(APIIdentityServiceContext, self).__call__()
if not ctxt:
return
for rid in relation_ids('neutron-plugin-api'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
ctxt['region'] = rdata.get('region')
if ctxt['region']:
return ctxt
return ctxt

View File

@ -8,6 +8,7 @@ from charmhelpers.core.hookenv import (
config, config,
log, log,
relation_set, relation_set,
relation_ids,
) )
from charmhelpers.core.host import ( from charmhelpers.core.host import (
@ -15,13 +16,18 @@ from charmhelpers.core.host import (
) )
from charmhelpers.fetch import ( from charmhelpers.fetch import (
apt_install, apt_update apt_install, apt_update, apt_purge
) )
from neutron_ovs_utils import ( from neutron_ovs_utils import (
DVR_PACKAGES,
configure_ovs,
determine_packages, determine_packages,
determine_dvr_packages,
get_shared_secret,
register_configs, register_configs,
restart_map, restart_map,
use_dvr,
) )
hooks = Hooks() hooks = Hooks()
@ -37,13 +43,40 @@ def install():
@hooks.hook('neutron-plugin-relation-changed') @hooks.hook('neutron-plugin-relation-changed')
@hooks.hook('neutron-plugin-api-relation-changed')
@hooks.hook('config-changed') @hooks.hook('config-changed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def config_changed(): def config_changed():
if determine_dvr_packages():
apt_update()
apt_install(determine_dvr_packages(), fatal=True)
configure_ovs()
CONFIGS.write_all() CONFIGS.write_all()
@hooks.hook('neutron-plugin-api-relation-changed')
@restart_on_change(restart_map())
def neutron_plugin_api_changed():
if use_dvr():
apt_update()
apt_install(DVR_PACKAGES, fatal=True)
else:
apt_purge(DVR_PACKAGES, fatal=True)
configure_ovs()
CONFIGS.write_all()
# If dvr setting has changed, need to pass that on
for rid in relation_ids('neutron-plugin'):
neutron_plugin_joined(relation_id=rid)
@hooks.hook('neutron-plugin-relation-joined')
def neutron_plugin_joined(relation_id=None):
secret = get_shared_secret() if use_dvr() else None
rel_data = {
'metadata-shared-secret': secret,
}
relation_set(relation_id=relation_id, **rel_data)
@hooks.hook('amqp-relation-joined') @hooks.hook('amqp-relation-joined')
def amqp_joined(relation_id=None): def amqp_joined(relation_id=None):
relation_set(relation_id=relation_id, relation_set(relation_id=relation_id,

View File

@ -7,12 +7,36 @@ from charmhelpers.contrib.openstack.utils import (
os_release, os_release,
) )
import neutron_ovs_context import neutron_ovs_context
from charmhelpers.contrib.network.ovs import (
add_bridge,
add_bridge_port,
full_restart,
)
from charmhelpers.core.hookenv import (
config,
)
from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
)
from charmhelpers.contrib.openstack.context import (
ExternalPortContext,
DataPortContext,
)
from charmhelpers.core.host import (
service_restart,
service_running,
)
NOVA_CONF_DIR = "/etc/nova" NOVA_CONF_DIR = "/etc/nova"
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
NEUTRON_DEFAULT = '/etc/default/neutron-server' NEUTRON_DEFAULT = '/etc/default/neutron-server'
NEUTRON_L3_AGENT_CONF = "/etc/neutron/l3_agent.ini"
NEUTRON_FWAAS_CONF = "/etc/neutron/fwaas_driver.ini"
ML2_CONF = '%s/plugins/ml2/ml2_conf.ini' % NEUTRON_CONF_DIR ML2_CONF = '%s/plugins/ml2/ml2_conf.ini' % NEUTRON_CONF_DIR
EXT_PORT_CONF = '/etc/init/ext-port.conf'
NEUTRON_METADATA_AGENT_CONF = "/etc/neutron/metadata_agent.ini"
DVR_PACKAGES = ['neutron-l3-agent']
PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf' PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates/' TEMPLATES = 'templates/'
@ -31,10 +55,41 @@ BASE_RESOURCE_MAP = OrderedDict([
'contexts': [context.PhyNICMTUContext()], 'contexts': [context.PhyNICMTUContext()],
}), }),
]) ])
DVR_RESOURCE_MAP = OrderedDict([
(NEUTRON_L3_AGENT_CONF, {
'services': ['neutron-l3-agent'],
'contexts': [neutron_ovs_context.L3AgentContext()],
}),
(NEUTRON_FWAAS_CONF, {
'services': ['neutron-l3-agent'],
'contexts': [neutron_ovs_context.L3AgentContext()],
}),
(EXT_PORT_CONF, {
'services': ['neutron-l3-agent'],
'contexts': [context.ExternalPortContext()],
}),
(NEUTRON_METADATA_AGENT_CONF, {
'services': ['neutron-metadata-agent'],
'contexts': [neutron_ovs_context.DVRSharedSecretContext(),
neutron_ovs_context.APIIdentityServiceContext()],
}),
])
TEMPLATES = 'templates/'
INT_BRIDGE = "br-int"
EXT_BRIDGE = "br-ex"
DATA_BRIDGE = 'br-data'
def determine_dvr_packages():
if use_dvr():
return DVR_PACKAGES
return []
def determine_packages(): def determine_packages():
return neutron_plugin_attribute('ovs', 'packages', 'neutron') pkgs = neutron_plugin_attribute('ovs', 'packages', 'neutron')
pkgs.extend(determine_dvr_packages())
return pkgs
def register_configs(release=None): def register_configs(release=None):
@ -52,6 +107,10 @@ def resource_map():
hook execution. hook execution.
''' '''
resource_map = deepcopy(BASE_RESOURCE_MAP) resource_map = deepcopy(BASE_RESOURCE_MAP)
if use_dvr():
resource_map.update(DVR_RESOURCE_MAP)
dvr_services = ['neutron-metadata-agent', 'neutron-l3-agent']
resource_map[NEUTRON_CONF]['services'] += dvr_services
return resource_map return resource_map
@ -61,3 +120,38 @@ def restart_map():
state. state.
''' '''
return {k: v['services'] for k, v in resource_map().iteritems()} return {k: v['services'] for k, v in resource_map().iteritems()}
def configure_ovs():
if not service_running('openvswitch-switch'):
full_restart()
add_bridge(INT_BRIDGE)
add_bridge(EXT_BRIDGE)
ext_port_ctx = None
if use_dvr():
ext_port_ctx = ExternalPortContext()()
if ext_port_ctx and ext_port_ctx['ext_port']:
add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port'])
portmaps = DataPortContext()()
bridgemaps = parse_bridge_mappings(config('bridge-mappings'))
for provider, br in bridgemaps.iteritems():
add_bridge(br)
if not portmaps or br not in portmaps:
continue
add_bridge_port(br, portmaps[br], promisc=True)
# Ensure this runs so that mtu is applied to data-port interfaces if
# provided.
service_restart('os-charm-phy-nic-mtu')
def get_shared_secret():
ctxt = neutron_ovs_context.DVRSharedSecretContext()()
if 'shared_secret' in ctxt:
return ctxt['shared_secret']
def use_dvr():
return context.NeutronAPIContext()()['enable_dvr']

16
templates/ext-port.conf Normal file
View File

@ -0,0 +1,16 @@
description "Enabling Neutron external networking port"
start on runlevel [2345]
task
script
EXT_PORT="{{ ext_port }}"
MTU="{{ ext_port_mtu }}"
if [ -n "$EXT_PORT" ]; then
ip link set $EXT_PORT up
if [ -n "$MTU" ]; then
ip link set $EXT_PORT mtu $MTU
fi
fi
end script

View File

@ -0,0 +1,7 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[fwaas]
driver = neutron.services.firewall.drivers.linux.iptables_fwaas.IptablesFwaasDriver
enabled = True

View File

@ -0,0 +1,7 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
agent_mode = {{ agent_mode }}

View File

@ -0,0 +1,20 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
# Metadata service seems to cache neutron api url from keystone so trigger
# restart if it changes: {{ quantum_url }}
[DEFAULT]
auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0
auth_region = {{ region }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
state_path = /var/lib/neutron
# Gateway runs a metadata API server locally
#nova_metadata_ip = {{ local_ip }}
nova_metadata_port = 8775
metadata_proxy_shared_secret = {{ shared_secret }}
cache_url = memory://?default_ttl=5

View File

@ -0,0 +1,43 @@
# juno
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
# Config managed by neutron-openvswitch charm
###############################################################################
[ml2]
type_drivers = gre,vxlan,vlan,flat
tenant_network_types = gre,vxlan,vlan,flat
mechanism_drivers = openvswitch,hyperv,l2population
[ml2_type_gre]
tunnel_id_ranges = 1:1000
[ml2_type_vxlan]
vni_ranges = 1001:2000
[ml2_type_vlan]
network_vlan_ranges = {{ vlan_ranges }}
[ml2_type_flat]
flat_networks = {{ network_providers }}
[ovs]
enable_tunneling = True
local_ip = {{ local_ip }}
bridge_mappings = {{ bridge_mappings }}
[agent]
tunnel_types = {{ overlay_network_type }}
l2_population = {{ l2_population }}
enable_distributed_routing = {{ distributed_routing }}
{% if veth_mtu -%}
veth_mtu = {{ veth_mtu }}
{% endif %}
[securitygroup]
{% if neutron_security_groups -%}
enable_security_group = True
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% else -%}
enable_security_group = False
{% endif -%}

View File

@ -0,0 +1,8 @@
# kilo
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[fwaas]
driver = neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas.IptablesFwaasDriver
enabled = True

View File

@ -1,20 +1,26 @@
from test_utils import CharmTestCase from test_utils import CharmTestCase
from test_utils import patch_open
from mock import patch from mock import patch
import neutron_ovs_context as context import neutron_ovs_context as context
import charmhelpers import charmhelpers
TO_PATCH = [ TO_PATCH = [
'resolve_address',
'config', 'config',
'unit_get', 'unit_get',
'add_bridge',
'add_bridge_port',
'service_running',
'service_start',
'service_restart',
'get_host_ip', 'get_host_ip',
] ]
def fake_context(settings):
def outer():
def inner():
return settings
return inner
return outer
class OVSPluginContextTest(CharmTestCase): class OVSPluginContextTest(CharmTestCase):
def setUp(self): def setUp(self):
@ -34,8 +40,10 @@ class OVSPluginContextTest(CharmTestCase):
self.test_config.set('data-port', 'br-data:em1') self.test_config.set('data-port', 'br-data:em1')
config.side_effect = self.test_config.get config.side_effect = self.test_config.get
mock_resolve_ports.side_effect = lambda ports: ports mock_resolve_ports.side_effect = lambda ports: ports
self.assertEquals(context.DataPortContext()(), self.assertEquals(
{'br-data': 'em1'}) charmhelpers.contrib.openstack.context.DataPortContext()(),
{'br-data': 'em1'}
)
@patch('charmhelpers.contrib.openstack.context.config') @patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr') @patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr')
@ -52,28 +60,10 @@ class OVSPluginContextTest(CharmTestCase):
config.side_effect = self.test_config.get config.side_effect = self.test_config.get
list_nics.return_value = machine_machs.keys() list_nics.return_value = machine_machs.keys()
get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic] get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic]
self.assertEquals(context.DataPortContext()(), self.assertEquals(
{'br-d2': 'em1'}) charmhelpers.contrib.openstack.context.DataPortContext()(),
{'br-d2': 'em1'}
@patch('charmhelpers.contrib.openstack.context.config') )
@patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
'resolve_ports')
def test_ensure_bridge_data_port_present(self, mock_resolve_ports, config):
self.test_config.set('data-port', 'br-data:em1')
self.test_config.set('bridge-mappings', 'phybr1:br-data')
config.side_effect = self.test_config.get
def add_port(bridge, port, promisc):
if bridge == 'br-data' and port == 'em1' and promisc is True:
self.bridge_added = True
return
self.bridge_added = False
mock_resolve_ports.side_effect = lambda ports: ports
self.add_bridge_port.side_effect = add_port
context.OVSPluginContext()._ensure_bridge()
self.assertEquals(self.bridge_added, True)
@patch.object(charmhelpers.contrib.openstack.context, 'relation_get') @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_ids') @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
@ -107,14 +97,15 @@ class OVSPluginContextTest(CharmTestCase):
'l2-population': 'True', 'l2-population': 'True',
'network-device-mtu': 1500, 'network-device-mtu': 1500,
'overlay-network-type': 'gre', 'overlay-network-type': 'gre',
'enable-dvr': 'True',
} }
_rget.side_effect = lambda *args, **kwargs: rdata _rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15' self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
expect = { expect = {
'neutron_alchemy_flags': {}, 'neutron_alchemy_flags': {},
'neutron_security_groups': True, 'neutron_security_groups': True,
'distributed_routing': True,
'verbose': True, 'verbose': True,
'local_ip': '127.0.0.15', 'local_ip': '127.0.0.15',
'network_device_mtu': 1500, 'network_device_mtu': 1500,
@ -133,7 +124,6 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000', 'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) self.assertEquals(expect, napi_ctxt())
self.service_start.assertCalled()
@patch.object(charmhelpers.contrib.openstack.context, 'relation_get') @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_ids') @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
@ -176,9 +166,9 @@ class OVSPluginContextTest(CharmTestCase):
} }
_rget.side_effect = lambda *args, **kwargs: rdata _rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15' self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
expect = { expect = {
'distributed_routing': False,
'neutron_alchemy_flags': {}, 'neutron_alchemy_flags': {},
'neutron_security_groups': False, 'neutron_security_groups': False,
'verbose': True, 'verbose': True,
@ -199,4 +189,95 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000', 'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) self.assertEquals(expect, napi_ctxt())
self.service_start.assertCalled()
class L3AgentContextTest(CharmTestCase):
def setUp(self):
super(L3AgentContextTest, self).setUp(context, TO_PATCH)
self.config.side_effect = self.test_config.get
def tearDown(self):
super(L3AgentContextTest, self).tearDown()
@patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
@patch.object(charmhelpers.contrib.openstack.context, 'related_units')
def test_dvr_enabled(self, _runits, _rids, _rget):
_runits.return_value = ['unit1']
_rids.return_value = ['rid2']
rdata = {
'neutron-security-groups': 'True',
'enable-dvr': 'True',
'l2-population': 'True',
'overlay-network-type': 'vxlan',
'network-device-mtu': 1500,
}
_rget.side_effect = lambda *args, **kwargs: rdata
self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'dvr'})
@patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
@patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
@patch.object(charmhelpers.contrib.openstack.context, 'related_units')
def test_dvr_disabled(self, _runits, _rids, _rget):
_runits.return_value = ['unit1']
_rids.return_value = ['rid2']
rdata = {
'neutron-security-groups': 'True',
'enable-dvr': 'False',
'l2-population': 'True',
'overlay-network-type': 'vxlan',
'network-device-mtu': 1500,
}
_rget.side_effect = lambda *args, **kwargs: rdata
self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'legacy'})
class DVRSharedSecretContext(CharmTestCase):
def setUp(self):
super(DVRSharedSecretContext, self).setUp(context,
TO_PATCH)
self.config.side_effect = self.test_config.get
@patch('os.path')
@patch('uuid.uuid4')
def test_secret_created_stored(self, _uuid4, _path):
_path.exists.return_value = False
_uuid4.return_value = 'secret_thing'
with patch_open() as (_open, _file):
self.assertEquals(context.get_shared_secret(),
'secret_thing')
_open.assert_called_with(
context.SHARED_SECRET.format('quantum'), 'w')
_file.write.assert_called_with('secret_thing')
@patch('os.path')
def test_secret_retrieved(self, _path):
_path.exists.return_value = True
with patch_open() as (_open, _file):
_file.read.return_value = 'secret_thing\n'
self.assertEquals(context.get_shared_secret(),
'secret_thing')
_open.assert_called_with(
context.SHARED_SECRET.format('quantum'), 'r')
@patch.object(context, 'NeutronAPIContext')
@patch.object(context, 'get_shared_secret')
def test_shared_secretcontext_dvr(self, _shared_secret,
_NeutronAPIContext):
_NeutronAPIContext.side_effect = fake_context({'enable_dvr': True})
_shared_secret.return_value = 'secret_thing'
self.resolve_address.return_value = '10.0.0.10'
self.assertEquals(context.DVRSharedSecretContext()(),
{'shared_secret': 'secret_thing',
'local_ip': '10.0.0.10'})
@patch.object(context, 'NeutronAPIContext')
@patch.object(context, 'get_shared_secret')
def test_shared_secretcontext_nodvr(self, _shared_secret,
_NeutronAPIContext):
_NeutronAPIContext.side_effect = fake_context({'enable_dvr': False})
_shared_secret.return_value = 'secret_thing'
self.resolve_address.return_value = '10.0.0.10'
self.assertEquals(context.DVRSharedSecretContext()(), {})

View File

@ -2,7 +2,6 @@
from mock import MagicMock, patch, call from mock import MagicMock, patch, call
from test_utils import CharmTestCase from test_utils import CharmTestCase
with patch('charmhelpers.core.hookenv.config') as config: with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron' config.return_value = 'neutron'
import neutron_ovs_utils as utils import neutron_ovs_utils as utils
@ -21,11 +20,17 @@ utils.restart_map = _map
TO_PATCH = [ TO_PATCH = [
'apt_update', 'apt_update',
'apt_install', 'apt_install',
'apt_purge',
'config', 'config',
'CONFIGS', 'CONFIGS',
'determine_packages', 'determine_packages',
'determine_dvr_packages',
'get_shared_secret',
'log', 'log',
'relation_ids',
'relation_set', 'relation_set',
'configure_ovs',
'use_dvr',
] ]
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
@ -56,6 +61,36 @@ class NeutronOVSHooksTests(CharmTestCase):
def test_config_changed(self): def test_config_changed(self):
self._call_hook('config-changed') self._call_hook('config-changed')
self.assertTrue(self.CONFIGS.write_all.called) self.assertTrue(self.CONFIGS.write_all.called)
self.configure_ovs.assert_called_with()
def test_config_changed_dvr(self):
self.determine_dvr_packages.return_value = ['dvr']
self._call_hook('config-changed')
self.apt_update.assert_called_with()
self.assertTrue(self.CONFIGS.write_all.called)
self.apt_install.assert_has_calls([
call(['dvr'], fatal=True),
])
self.configure_ovs.assert_called_with()
@patch.object(hooks, 'neutron_plugin_joined')
def test_neutron_plugin_api(self, _plugin_joined):
self.relation_ids.return_value = ['rid']
self._call_hook('neutron-plugin-api-relation-changed')
self.configure_ovs.assert_called_with()
self.assertTrue(self.CONFIGS.write_all.called)
_plugin_joined.assert_called_with(relation_id='rid')
def test_neutron_plugin_joined(self):
self.get_shared_secret.return_value = 'secret'
self._call_hook('neutron-plugin-relation-joined')
rel_data = {
'metadata-shared-secret': 'secret',
}
self.relation_set.assert_called_with(
relation_id=None,
**rel_data
)
def test_amqp_joined(self): def test_amqp_joined(self):
self._call_hook('amqp-relation-joined') self._call_hook('amqp-relation-joined')

View File

@ -1,11 +1,12 @@
from mock import MagicMock, patch from mock import MagicMock, patch, call
from collections import OrderedDict from collections import OrderedDict
import charmhelpers.contrib.openstack.templating as templating import charmhelpers.contrib.openstack.templating as templating
templating.OSConfigRenderer = MagicMock() templating.OSConfigRenderer = MagicMock()
import neutron_ovs_utils as nutils import neutron_ovs_utils as nutils
import neutron_ovs_context
from test_utils import ( from test_utils import (
CharmTestCase, CharmTestCase,
@ -15,8 +16,15 @@ import charmhelpers.core.hookenv as hookenv
TO_PATCH = [ TO_PATCH = [
'add_bridge',
'add_bridge_port',
'config',
'os_release', 'os_release',
'neutron_plugin_attribute', 'neutron_plugin_attribute',
'full_restart',
'service_restart',
'service_running',
'ExternalPortContext',
] ]
head_pkg = 'linux-headers-3.15.0-5-generic' head_pkg = 'linux-headers-3.15.0-5-generic'
@ -38,26 +46,39 @@ def _mock_npa(plugin, attr, net_manager=None):
return plugins[plugin][attr] return plugins[plugin][attr]
class DummyContext():
def __init__(self, return_value):
self.return_value = return_value
def __call__(self):
return self.return_value
class TestNeutronOVSUtils(CharmTestCase): class TestNeutronOVSUtils(CharmTestCase):
def setUp(self): def setUp(self):
super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH) super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH)
self.neutron_plugin_attribute.side_effect = _mock_npa self.neutron_plugin_attribute.side_effect = _mock_npa
self.config.side_effect = self.test_config.get
def tearDown(self): def tearDown(self):
# Reset cached cache # Reset cached cache
hookenv.cache = {} hookenv.cache = {}
@patch.object(nutils, 'use_dvr')
@patch.object(charmhelpers.contrib.openstack.neutron, 'os_release') @patch.object(charmhelpers.contrib.openstack.neutron, 'os_release')
@patch.object(charmhelpers.contrib.openstack.neutron, 'headers_package') @patch.object(charmhelpers.contrib.openstack.neutron, 'headers_package')
def test_determine_packages(self, _head_pkgs, _os_rel): def test_determine_packages(self, _head_pkgs, _os_rel, _use_dvr):
_use_dvr.return_value = False
_os_rel.return_value = 'trusty' _os_rel.return_value = 'trusty'
_head_pkgs.return_value = head_pkg _head_pkgs.return_value = head_pkg
pkg_list = nutils.determine_packages() pkg_list = nutils.determine_packages()
expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]] expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]]
self.assertItemsEqual(pkg_list, expect) self.assertItemsEqual(pkg_list, expect)
def test_register_configs(self): @patch.object(nutils, 'use_dvr')
def test_register_configs(self, _use_dvr):
class _mock_OSConfigRenderer(): class _mock_OSConfigRenderer():
def __init__(self, templates_dir=None, openstack_release=None): def __init__(self, templates_dir=None, openstack_release=None):
self.configs = [] self.configs = []
@ -67,6 +88,7 @@ class TestNeutronOVSUtils(CharmTestCase):
self.configs.append(config) self.configs.append(config)
self.ctxts.append(ctxt) self.ctxts.append(ctxt)
_use_dvr.return_value = False
self.os_release.return_value = 'trusty' self.os_release.return_value = 'trusty'
templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
_regconfs = nutils.register_configs() _regconfs = nutils.register_configs()
@ -75,12 +97,28 @@ class TestNeutronOVSUtils(CharmTestCase):
'/etc/init/os-charm-phy-nic-mtu.conf'] '/etc/init/os-charm-phy-nic-mtu.conf']
self.assertItemsEqual(_regconfs.configs, confs) self.assertItemsEqual(_regconfs.configs, confs)
def test_resource_map(self): @patch.object(nutils, 'use_dvr')
def test_resource_map(self, _use_dvr):
_use_dvr.return_value = False
_map = nutils.resource_map() _map = nutils.resource_map()
svcs = ['neutron-plugin-openvswitch-agent']
confs = [nutils.NEUTRON_CONF] confs = [nutils.NEUTRON_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs] [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs)
def test_restart_map(self): @patch.object(nutils, 'use_dvr')
def test_resource_map_dvr(self, _use_dvr):
_use_dvr.return_value = True
_map = nutils.resource_map()
svcs = ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent',
'neutron-l3-agent']
confs = [nutils.NEUTRON_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs]
self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs)
@patch.object(nutils, 'use_dvr')
def test_restart_map(self, _use_dvr):
_use_dvr.return_value = False
_restart_map = nutils.restart_map() _restart_map = nutils.restart_map()
ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini" ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini"
expect = OrderedDict([ expect = OrderedDict([
@ -92,3 +130,79 @@ class TestNeutronOVSUtils(CharmTestCase):
for item in _restart_map: for item in _restart_map:
self.assertTrue(item in _restart_map) self.assertTrue(item in _restart_map)
self.assertTrue(expect[item] == _restart_map[item]) self.assertTrue(expect[item] == _restart_map[item])
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_ovs_data_port(self, mock_config, _use_dvr):
_use_dvr.return_value = False
mock_config.side_effect = self.test_config.get
self.config.side_effect = self.test_config.get
self.ExternalPortContext.return_value = \
DummyContext(return_value=None)
# Test back-compatibility i.e. port but no bridge (so br-data is
# assumed)
self.test_config.set('data-port', 'eth0')
nutils.configure_ovs()
self.add_bridge.assert_has_calls([
call('br-int'),
call('br-ex'),
call('br-data')
])
self.assertTrue(self.add_bridge_port.called)
# Now test with bridge:port format
self.test_config.set('data-port', 'br-foo:eth0')
self.add_bridge.reset_mock()
self.add_bridge_port.reset_mock()
nutils.configure_ovs()
self.add_bridge.assert_has_calls([
call('br-int'),
call('br-ex'),
call('br-data')
])
# Not called since we have a bogus bridge in data-ports
self.assertFalse(self.add_bridge_port.called)
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_starts_service_if_required(self, mock_config,
_use_dvr):
_use_dvr.return_value = False
mock_config.side_effect = self.test_config.get
self.config.return_value = 'ovs'
self.service_running.return_value = False
nutils.configure_ovs()
self.assertTrue(self.full_restart.called)
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_doesnt_restart_service(self, mock_config, _use_dvr):
_use_dvr.return_value = False
mock_config.side_effect = self.test_config.get
self.config.side_effect = self.test_config.get
self.service_running.return_value = True
nutils.configure_ovs()
self.assertFalse(self.full_restart.called)
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_ovs_ext_port(self, mock_config, _use_dvr):
_use_dvr.return_value = True
mock_config.side_effect = self.test_config.get
self.config.side_effect = self.test_config.get
self.test_config.set('ext-port', 'eth0')
self.ExternalPortContext.return_value = \
DummyContext(return_value={'ext_port': 'eth0'})
nutils.configure_ovs()
self.add_bridge.assert_has_calls([
call('br-int'),
call('br-ex'),
call('br-data')
])
self.add_bridge_port.assert_called_with('br-ex', 'eth0')
@patch.object(neutron_ovs_context, 'DVRSharedSecretContext')
def test_get_shared_secret(self, _dvr_secret_ctxt):
_dvr_secret_ctxt.return_value = \
DummyContext(return_value={'shared_secret': 'supersecret'})
self.assertEqual(nutils.get_shared_secret(), 'supersecret')