[project-calico,r=james-page] Add support for Calico plugin

This commit is contained in:
James Page 2015-09-04 12:03:14 +01:00
commit f87ad07e29
18 changed files with 251 additions and 4 deletions

View File

@ -99,6 +99,7 @@ options:
. .
ovs - OpenvSwitch Plugin ovs - OpenvSwitch Plugin
nsx - VMWare NSX nsx - VMWare NSX
Calico - Project Calico Networking
. .
overlay-network-type: overlay-network-type:
default: gre default: gre
@ -388,3 +389,12 @@ options:
description: | description: |
If True neutron-server will install neutron packages for the plugin If True neutron-server will install neutron packages for the plugin
configured. configured.
# Calico plugin configuration
calico-origin:
default:
type: string
description: |
Repository from which to install Calico packages. If set, must be
a PPA URL, of the form ppa:somecustom/ppa. Changing this value
after installation will force an immediate software upgrade.
# End of Calico plugin configuration

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

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

View File

@ -240,6 +240,28 @@ class HAProxyContext(context.HAProxyContext):
return ctxt return ctxt
class EtcdContext(context.OSContextGenerator):
interfaces = ['etcd-proxy']
def __call__(self):
ctxt = {'cluster': ''}
cluster_string = ''
if not config('neutron-plugin') == 'Calico':
return ctxt
for rid in relation_ids('etcd-proxy'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
cluster_string = rdata.get('cluster')
if cluster_string:
break
ctxt['cluster'] = cluster_string
return ctxt
class NeutronApiSDNContext(context.SubordinateConfigContext): class NeutronApiSDNContext(context.SubordinateConfigContext):
interfaces = 'neutron-plugin-api-subordinate' interfaces = 'neutron-plugin-api-subordinate'

View File

@ -60,6 +60,8 @@ from neutron_api_utils import (
services, services,
setup_ipv6, setup_ipv6,
get_topics, get_topics,
additional_install_locations,
force_etcd_restart,
) )
from neutron_api_context import ( from neutron_api_context import (
get_dvr, get_dvr,
@ -67,6 +69,7 @@ from neutron_api_context import (
get_l2population, get_l2population,
get_overlay_network_type, get_overlay_network_type,
IdentityServiceContext, IdentityServiceContext,
EtcdContext,
) )
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
@ -143,6 +146,9 @@ def configure_https():
def install(): def install():
execd_preinstall() execd_preinstall()
configure_installation_source(config('openstack-origin')) configure_installation_source(config('openstack-origin'))
additional_install_locations(
config('neutron-plugin'), config('openstack-origin')
)
apt_update() apt_update()
apt_install(determine_packages(config('openstack-origin')), apt_install(determine_packages(config('openstack-origin')),
@ -183,6 +189,10 @@ def config_changed():
if openstack_upgrade_available('neutron-common'): if openstack_upgrade_available('neutron-common'):
do_openstack_upgrade(CONFIGS) do_openstack_upgrade(CONFIGS)
additional_install_locations(
config('neutron-plugin'),
config('openstack-origin')
)
apt_install(filter_installed_packages( apt_install(filter_installed_packages(
determine_packages(config('openstack-origin'))), determine_packages(config('openstack-origin'))),
fatal=True) fatal=True)
@ -352,6 +362,7 @@ def neutron_plugin_api_relation_joined(rid=None):
'enable-dvr': get_dvr(), 'enable-dvr': get_dvr(),
'enable-l3ha': get_l3ha(), 'enable-l3ha': get_l3ha(),
'overlay-network-type': get_overlay_network_type(), 'overlay-network-type': get_overlay_network_type(),
'addr': unit_get('private-address'),
} }
# Provide this value to relations since it needs to be set in multiple # Provide this value to relations since it needs to be set in multiple
@ -500,6 +511,20 @@ def update_nrpe_config():
nrpe_setup.write() nrpe_setup.write()
@hooks.hook('etcd-proxy-relation-joined')
@hooks.hook('etcd-proxy-relation-changed')
def etcd_proxy_force_restart(relation_id=None):
# note(cory.benfield): Mostly etcd does not require active management,
# but occasionally it does require a full config nuking. This does not
# play well with the standard neutron-api config management, so we
# treat etcd like the special snowflake it insists on being.
CONFIGS.register('/etc/init/etcd.conf', [EtcdContext()])
CONFIGS.write('/etc/init/etcd.conf')
if 'etcd-proxy' in CONFIGS.complete_contexts():
force_etcd_restart()
def main(): def main():
try: try:
hooks.execute(sys.argv) hooks.execute(sys.argv)

View File

@ -4,6 +4,7 @@ from functools import partial
import os import os
import shutil import shutil
import subprocess import subprocess
import glob
from base64 import b64encode from base64 import b64encode
from charmhelpers.contrib.openstack import context, templating from charmhelpers.contrib.openstack import context, templating
from charmhelpers.contrib.openstack.neutron import ( from charmhelpers.contrib.openstack.neutron import (
@ -38,11 +39,13 @@ from charmhelpers.fetch import (
) )
from charmhelpers.core.host import ( from charmhelpers.core.host import (
lsb_release,
adduser, adduser,
add_group, add_group,
add_user_to_group, add_user_to_group,
mkdir, mkdir,
lsb_release, service_stop,
service_start,
service_restart, service_restart,
write_file, write_file,
) )
@ -160,6 +163,37 @@ def api_port(service):
return API_PORTS[service] return API_PORTS[service]
def additional_install_locations(plugin, source):
'''
Add any required additional package locations for the charm, based
on the Neutron plugin being used. This will also force an immediate
package upgrade.
'''
if plugin == 'Calico':
if config('calico-origin'):
calico_source = config('calico-origin')
else:
release = get_os_codename_install_source(source)
calico_source = 'ppa:project-calico/%s' % release
add_source(calico_source)
apt_update()
apt_upgrade()
def force_etcd_restart():
'''
If etcd has been reconfigured we need to force it to fully restart.
This is necessary because etcd has some config flags that it ignores
after the first time it starts, so we need to make it forget them.
'''
service_stop('etcd')
for directory in glob.glob('/var/lib/etcd/*'):
shutil.rmtree(directory)
service_start('etcd')
def manage_plugin(): def manage_plugin():
return config('manage-neutron-plugin-legacy-mode') return config('manage-neutron-plugin-legacy-mode')

View File

@ -40,6 +40,8 @@ requires:
neutron-plugin-api-subordinate: neutron-plugin-api-subordinate:
interface: neutron-plugin-api-subordinate interface: neutron-plugin-api-subordinate
scope: container scope: container
etcd-proxy:
interface: etcd-proxy
peers: peers:
cluster: cluster:
interface: neutron-api-ha interface: neutron-api-ha

View File

@ -0,0 +1,16 @@
# managed by juju, DO NOT EDIT
description "etcd"
author "etcd maintainers"
start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]
respawn
setuid etcd
env ETCD_DATA_DIR=/var/lib/etcd
export ETCD_DATA_DIR
exec /usr/bin/etcd -proxy on \
-initial-cluster {{ cluster }}

View File

@ -4,6 +4,10 @@
# Configuration file maintained by Juju. Local changes may be overwritten. # Configuration file maintained by Juju. Local changes may be overwritten.
############################################################################### ###############################################################################
[ml2] [ml2]
{% if neutron_plugin == 'Calico' -%}
type_drivers = local,flat
mechanism_drivers = calico
{% else -%}
type_drivers = {{ overlay_network_type }},vlan,flat type_drivers = {{ overlay_network_type }},vlan,flat
tenant_network_types = {{ overlay_network_type }},vlan,flat tenant_network_types = {{ overlay_network_type }},vlan,flat
mechanism_drivers = openvswitch,hyperv,l2population mechanism_drivers = openvswitch,hyperv,l2population
@ -26,11 +30,16 @@ local_ip = {{ local_ip }}
[agent] [agent]
tunnel_types = {{ overlay_network_type }} tunnel_types = {{ overlay_network_type }}
{% endif -%}
[securitygroup] [securitygroup]
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
enable_security_group = True enable_security_group = True
{% if neutron_plugin == 'Calico' -%}
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
{% else -%}
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%}
{% else -%} {% else -%}
enable_security_group = False enable_security_group = False
{% endif -%} {% endif -%}

View File

@ -30,7 +30,7 @@ core_plugin = {{ core_plugin }}
{% if service_plugins -%} {% if service_plugins -%}
service_plugins = {{ service_plugins }} service_plugins = {{ service_plugins }}
{% else -%} {% else -%}
{% if neutron_plugin in ['ovs', 'ml2'] -%} {% 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 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 -%}
@ -38,8 +38,16 @@ service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neu
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
{% if neutron_plugin == 'Calico' -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
{% else -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%} {% endif -%}
{% endif -%}
{% if neutron_plugin == 'Calico' -%}
dhcp_agents_per_network = 1000
{% endif -%}
{% include "parts/rabbitmq" %} {% include "parts/rabbitmq" %}

View File

@ -31,15 +31,23 @@ bind_port = 9696
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% if neutron_plugin in ['ovs', 'ml2'] -%} {% 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 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 -%}
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
{% if neutron_plugin == 'Calico' -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
{% else -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%} {% endif -%}
{% endif -%}
{% if neutron_plugin == 'Calico' -%}
dhcp_agents_per_network = 1000
{% endif -%}
{% include "parts/rabbitmq" %} {% include "parts/rabbitmq" %}

View File

@ -4,6 +4,10 @@
# Configuration file maintained by Juju. Local changes may be overwritten. # Configuration file maintained by Juju. Local changes may be overwritten.
############################################################################### ###############################################################################
[ml2] [ml2]
{% if neutron_plugin == 'Calico' -%}
type_drivers = local,flat
mechanism_drivers = calico
{% else -%}
type_drivers = {{ overlay_network_type }},vlan,flat type_drivers = {{ overlay_network_type }},vlan,flat
tenant_network_types = {{ overlay_network_type }},vlan,flat tenant_network_types = {{ overlay_network_type }},vlan,flat
mechanism_drivers = openvswitch,l2population mechanism_drivers = openvswitch,l2population
@ -26,11 +30,16 @@ local_ip = {{ local_ip }}
[agent] [agent]
tunnel_types = {{ overlay_network_type }} tunnel_types = {{ overlay_network_type }}
{% endif -%}
[securitygroup] [securitygroup]
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
enable_security_group = True enable_security_group = True
{% if neutron_plugin == 'Calico' -%}
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
{% else -%}
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%}
{% else -%} {% else -%}
enable_security_group = False enable_security_group = False
{% endif -%} {% endif -%}

View File

@ -34,7 +34,7 @@ core_plugin = {{ core_plugin }}
{% if service_plugins -%} {% if service_plugins -%}
service_plugins = {{ service_plugins }} service_plugins = {{ service_plugins }}
{% else -%} {% else -%}
{% if neutron_plugin in ['ovs', 'ml2'] -%} {% if neutron_plugin in ['ovs', 'ml2', 'Calico'] -%}
service_plugins = router,firewall,lbaas,vpnaas,metering service_plugins = router,firewall,lbaas,vpnaas,metering
{% endif -%} {% endif -%}
{% endif -%} {% endif -%}
@ -42,8 +42,16 @@ service_plugins = router,firewall,lbaas,vpnaas,metering
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
{% if neutron_plugin == 'Calico' -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
{% else -%}
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%} {% endif -%}
{% endif -%}
{% if neutron_plugin == 'Calico' -%}
dhcp_agents_per_network = 1000
{% endif -%}
notify_nova_on_port_status_changes = True notify_nova_on_port_status_changes = True
notify_nova_on_port_data_changes = True notify_nova_on_port_data_changes = True

View File

@ -438,6 +438,53 @@ class NeutronCCContextTest(CharmTestCase):
self.assertEquals(napi_ctxt[key], expect[key]) self.assertEquals(napi_ctxt[key], expect[key])
class EtcdContextTest(CharmTestCase):
def setUp(self):
super(EtcdContextTest, self).setUp(context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get
self.test_config.set('neutron-plugin', 'Calico')
def tearDown(self):
super(EtcdContextTest, self).tearDown()
def test_etcd_no_related_units(self):
self.related_units.return_value = []
ctxt = context.EtcdContext()()
expect = {'cluster': ''}
self.assertEquals(expect, ctxt)
def test_some_related_units(self):
self.related_units.return_value = ['unit1']
self.relation_ids.return_value = ['rid2', 'rid3']
result = (
'testname=http://172.18.18.18:8888,'
'testname=http://172.18.18.18:8888'
)
self.test_relation.set({'cluster': result})
ctxt = context.EtcdContext()()
expect = {'cluster': result}
self.assertEquals(expect, ctxt)
def test_early_exit(self):
self.test_config.set('neutron-plugin', 'notCalico')
self.related_units.return_value = ['unit1']
self.relation_ids.return_value = ['rid2', 'rid3']
self.test_relation.set({'ip': '172.18.18.18',
'port': 8888,
'name': 'testname'})
ctxt = context.EtcdContext()()
expect = {'cluster': ''}
self.assertEquals(expect, ctxt)
class NeutronApiSDNContextTest(CharmTestCase): class NeutronApiSDNContextTest(CharmTestCase):
def setUp(self): def setUp(self):

View File

@ -62,6 +62,7 @@ TO_PATCH = [
'update_nrpe_config', 'update_nrpe_config',
'service_reload', 'service_reload',
'IdentityServiceContext', 'IdentityServiceContext',
'force_etcd_restart',
] ]
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
@ -440,12 +441,14 @@ class NeutronAPIHooksTests(CharmTestCase):
self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF)) self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF))
def test_neutron_plugin_api_relation_joined_nol2(self): def test_neutron_plugin_api_relation_joined_nol2(self):
self.unit_get.return_value = '172.18.18.18'
self.IdentityServiceContext.return_value = \ self.IdentityServiceContext.return_value = \
DummyContext(return_value={}) DummyContext(return_value={})
_relation_data = { _relation_data = {
'neutron-security-groups': False, 'neutron-security-groups': False,
'enable-dvr': False, 'enable-dvr': False,
'enable-l3ha': False, 'enable-l3ha': False,
'addr': '172.18.18.18',
'l2-population': False, 'l2-population': False,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
'service_protocol': None, 'service_protocol': None,
@ -470,12 +473,14 @@ class NeutronAPIHooksTests(CharmTestCase):
) )
def test_neutron_plugin_api_relation_joined_dvr(self): def test_neutron_plugin_api_relation_joined_dvr(self):
self.unit_get.return_value = '172.18.18.18'
self.IdentityServiceContext.return_value = \ self.IdentityServiceContext.return_value = \
DummyContext(return_value={}) DummyContext(return_value={})
_relation_data = { _relation_data = {
'neutron-security-groups': False, 'neutron-security-groups': False,
'enable-dvr': True, 'enable-dvr': True,
'enable-l3ha': False, 'enable-l3ha': False,
'addr': '172.18.18.18',
'l2-population': True, 'l2-population': True,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
'service_protocol': None, 'service_protocol': None,
@ -500,12 +505,14 @@ class NeutronAPIHooksTests(CharmTestCase):
) )
def test_neutron_plugin_api_relation_joined_l3ha(self): def test_neutron_plugin_api_relation_joined_l3ha(self):
self.unit_get.return_value = '172.18.18.18'
self.IdentityServiceContext.return_value = \ self.IdentityServiceContext.return_value = \
DummyContext(return_value={}) DummyContext(return_value={})
_relation_data = { _relation_data = {
'neutron-security-groups': False, 'neutron-security-groups': False,
'enable-dvr': False, 'enable-dvr': False,
'enable-l3ha': True, 'enable-l3ha': True,
'addr': '172.18.18.18',
'l2-population': False, 'l2-population': False,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
'service_protocol': None, 'service_protocol': None,
@ -530,11 +537,13 @@ class NeutronAPIHooksTests(CharmTestCase):
) )
def test_neutron_plugin_api_relation_joined_w_mtu(self): def test_neutron_plugin_api_relation_joined_w_mtu(self):
self.unit_get.return_value = '172.18.18.18'
self.IdentityServiceContext.return_value = \ self.IdentityServiceContext.return_value = \
DummyContext(return_value={}) DummyContext(return_value={})
self.test_config.set('network-device-mtu', 1500) self.test_config.set('network-device-mtu', 1500)
_relation_data = { _relation_data = {
'neutron-security-groups': False, 'neutron-security-groups': False,
'addr': '172.18.18.18',
'l2-population': False, 'l2-population': False,
'overlay-network-type': 'vxlan', 'overlay-network-type': 'vxlan',
'network-device-mtu': 1500, 'network-device-mtu': 1500,
@ -750,3 +759,8 @@ class NeutronAPIHooksTests(CharmTestCase):
'Not running neutron database migration as migrations are handled ' 'Not running neutron database migration as migrations are handled '
'by the neutron-server process or nova-cloud-controller charm.' 'by the neutron-server process or nova-cloud-controller charm.'
) )
def test_etcd_peer_joined(self):
self._call_hook('etcd-proxy-relation-joined')
self.assertTrue(self.CONFIGS.register.called)
self.CONFIGS.write.assert_called_with('/etc/init/etcd.conf')

View File

@ -24,6 +24,7 @@ TO_PATCH = [
'apt_install', 'apt_install',
'apt_update', 'apt_update',
'apt_upgrade', 'apt_upgrade',
'add_source',
'b64encode', 'b64encode',
'config', 'config',
'configure_installation_source', 'configure_installation_source',
@ -34,6 +35,9 @@ TO_PATCH = [
'pip_install', 'pip_install',
'subprocess', 'subprocess',
'is_elected_leader', 'is_elected_leader',
'service_stop',
'service_start',
'glob',
] ]
openstack_origin_git = \ openstack_origin_git = \
@ -559,3 +563,30 @@ class TestNeutronAPIUtils(CharmTestCase):
self.test_config.set('manage-neutron-plugin-legacy-mode', False) self.test_config.set('manage-neutron-plugin-legacy-mode', False)
manage = nutils.manage_plugin() manage = nutils.manage_plugin()
self.assertFalse(manage) self.assertFalse(manage)
def test_additional_install_locations_calico(self):
self.get_os_codename_install_source.return_value = 'icehouse'
nutils.additional_install_locations('Calico', '')
self.add_source.assert_called_with('ppa:project-calico/icehouse')
def test_unusual_calico_install_location(self):
self.test_config.set('calico-origin', 'ppa:testppa/project-calico')
nutils.additional_install_locations('Calico', '')
self.add_source.assert_called_with('ppa:testppa/project-calico')
def test_follows_openstack_origin(self):
self.get_os_codename_install_source.return_value = 'juno'
nutils.additional_install_locations('Calico', 'cloud:trusty-juno')
self.add_source.assert_called_with('ppa:project-calico/juno')
@patch('shutil.rmtree')
def test_force_etcd_restart(self, rmtree):
self.glob.glob.return_value = [
'/var/lib/etcd/one', '/var/lib/etcd/two'
]
nutils.force_etcd_restart()
self.service_stop.assert_called_once_with('etcd')
self.glob.glob.assert_called_once_with('/var/lib/etcd/*')
rmtree.assert_any_call('/var/lib/etcd/one')
rmtree.assert_any_call('/var/lib/etcd/two')
self.service_start.assert_called_once_with('etcd')