From 3c7dd72d38828840c466f0f2c3093e1719e5aeda Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 2 Feb 2015 13:31:39 +0000 Subject: [PATCH 01/27] Add dvr support --- hooks/neutron_ovs_context.py | 32 +++++++++++++++++++++----- hooks/neutron_ovs_hooks.py | 3 +++ hooks/neutron_ovs_utils.py | 23 ++++++++++++++++++- templates/juno/fwaas_driver.ini | 7 ++++++ templates/juno/l3_agent.ini | 7 ++++++ templates/juno/ml2_conf.ini | 40 +++++++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 templates/juno/fwaas_driver.ini create mode 100644 templates/juno/l3_agent.ini create mode 100644 templates/juno/ml2_conf.ini diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 7dbf5211..197c4420 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -1,3 +1,4 @@ +import ast from charmhelpers.core.hookenv import ( relation_ids, related_units, @@ -11,6 +12,7 @@ from charmhelpers.core.host import service_running, service_start from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port from charmhelpers.contrib.openstack.utils import get_host_ip from charmhelpers.contrib.network.ip import get_address_in_network +from charmhelpers.contrib.openstack.context import OSContextGenerator import re @@ -26,17 +28,19 @@ def _neutron_api_settings(): 'neutron_security_groups': False, 'l2_population': True, 'overlay_network_type': 'gre', + 'enable_dvr': False, } for rid in relation_ids('neutron-plugin-api'): for unit in related_units(rid): rdata = relation_get(rid=rid, unit=unit) if 'l2-population' not in rdata: continue - neutron_settings = { - 'l2_population': rdata['l2-population'], - 'neutron_security_groups': rdata['neutron-security-groups'], - 'overlay_network_type': rdata['overlay-network-type'], - } + neutron_settings['l2_population'] = rdata['l2-population'] + if 'overlay-network-type' in rdata: + neutron_settings['overlay_network_type'] = \ + rdata['overlay-network-type'] + if 'enable-dvr' in rdata: + neutron_settings['enable_dvr'] = rdata['enable-dvr'] # Override with configuration if set to true if config('disable-security-groups'): neutron_settings['neutron_security_groups'] = False @@ -44,6 +48,11 @@ def _neutron_api_settings(): return neutron_settings +def use_dvr(): + api_settings = _neutron_api_settings() + return ast.literal_eval(api_settings['enable_dvr']) + + class OVSPluginContext(context.NeutronContext): interfaces = [] @@ -103,6 +112,7 @@ class OVSPluginContext(context.NeutronContext): neutron_api_settings = _neutron_api_settings() ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups ovs_ctxt['l2_population'] = neutron_api_settings['l2_population'] + ovs_ctxt['distributed_routing'] = use_dvr() ovs_ctxt['overlay_network_type'] = \ neutron_api_settings['overlay_network_type'] # TODO: We need to sort out the syslog and debug/verbose options as a @@ -111,3 +121,15 @@ class OVSPluginContext(context.NeutronContext): ovs_ctxt['verbose'] = conf['verbose'] ovs_ctxt['debug'] = conf['debug'] return ovs_ctxt + + +class L3AgentContext(OSContextGenerator): + + def __call__(self): + neutron_api_settings = _neutron_api_settings() + ctxt = {} + if neutron_api_settings['enable_dvr'] == 'True': + ctxt['agent_mode'] = 'dvr' + else: + ctxt['agent_mode'] = 'legacy' + return ctxt diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index eb53094d..ee314606 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -20,6 +20,7 @@ from charmhelpers.fetch import ( from neutron_ovs_utils import ( determine_packages, + determine_dvr_packages, register_configs, restart_map, ) @@ -41,6 +42,8 @@ def install(): @hooks.hook('config-changed') @restart_on_change(restart_map()) def config_changed(): + if determine_dvr_packages(): + apt_install(determine_dvr_packages(), fatal=True) CONFIGS.write_all() diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index b5d742de..c0381911 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -12,6 +12,8 @@ NOVA_CONF_DIR = "/etc/nova" NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR 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 BASE_RESOURCE_MAP = OrderedDict([ @@ -24,12 +26,29 @@ BASE_RESOURCE_MAP = OrderedDict([ 'services': ['neutron-plugin-openvswitch-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext()], }), + (NEUTRON_L3_AGENT_CONF, { + 'services': ['neutron-vpn-agent'], + 'contexts': [neutron_ovs_context.L3AgentContext()], + }), + (NEUTRON_FWAAS_CONF, { + 'services': ['neutron-vpn-agent'], + 'contexts': [neutron_ovs_context.L3AgentContext()], + }), ]) TEMPLATES = 'templates/' +def determine_dvr_packages(): + pkgs = [] + if neutron_ovs_context.use_dvr(): + pkgs = 'neutron-vpn-agent' + return pkgs + + 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): @@ -47,6 +66,8 @@ def resource_map(): hook execution. ''' resource_map = deepcopy(BASE_RESOURCE_MAP) + if not neutron_ovs_context.use_dvr(): + resource_map.pop(NEUTRON_L3_AGENT_CONF) return resource_map diff --git a/templates/juno/fwaas_driver.ini b/templates/juno/fwaas_driver.ini new file mode 100644 index 00000000..e64046dc --- /dev/null +++ b/templates/juno/fwaas_driver.ini @@ -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 diff --git a/templates/juno/l3_agent.ini b/templates/juno/l3_agent.ini new file mode 100644 index 00000000..8e93c71a --- /dev/null +++ b/templates/juno/l3_agent.ini @@ -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 }} diff --git a/templates/juno/ml2_conf.ini b/templates/juno/ml2_conf.ini new file mode 100644 index 00000000..1a0c7c93 --- /dev/null +++ b/templates/juno/ml2_conf.ini @@ -0,0 +1,40 @@ +# icehouse +############################################################################### +# [ 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 = physnet1:1000:2000 + +[ml2_type_flat] +flat_networks = physnet1 + +[ovs] +enable_tunneling = True +local_ip = {{ local_ip }} +bridge_mappings = physnet1:br-data + +[agent] +tunnel_types = {{ overlay_network_type }} +l2_population = {{ l2_population }} +enable_distributed_routing = {{ distributed_routing }} + +[securitygroup] +{% if neutron_security_groups -%} +enable_security_group = True +firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver +{% else -%} +enable_security_group = False +{% endif -%} From 5ab3daeb05e796558aa411e89a9f1b38ba5856e7 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 2 Feb 2015 15:12:56 +0000 Subject: [PATCH 02/27] Fix defaults as templates expect strings not booleans --- hooks/neutron_ovs_context.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 197c4420..f6305a7c 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -20,15 +20,21 @@ OVS_BRIDGE = 'br-int' DATA_BRIDGE = 'br-data' +def to_boolean(option): + if option is None: + return False + return ast.literal_eval(option) + + def _neutron_api_settings(): ''' Inspects current neutron-plugin relation ''' neutron_settings = { - 'neutron_security_groups': False, - 'l2_population': True, + 'neutron_security_groups': 'False', + 'l2_population': 'True', 'overlay_network_type': 'gre', - 'enable_dvr': False, + 'enable_dvr': 'False', } for rid in relation_ids('neutron-plugin-api'): for unit in related_units(rid): @@ -50,7 +56,7 @@ def _neutron_api_settings(): def use_dvr(): api_settings = _neutron_api_settings() - return ast.literal_eval(api_settings['enable_dvr']) + return to_boolean(api_settings['enable_dvr']) class OVSPluginContext(context.NeutronContext): From 2f1aceb741d4517bd7f0ecf5567a0c538c2371bd Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 2 Feb 2015 15:26:32 +0000 Subject: [PATCH 03/27] Add setup for configuring an external bridge --- config.yaml | 9 +++++++ hooks/neutron_ovs_context.py | 49 +++++++++++++++++++++++++++++++++++- hooks/neutron_ovs_utils.py | 22 ++++++++++++++++ templates/ext-port.conf | 9 +++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 templates/ext-port.conf diff --git a/config.yaml b/config.yaml index ea3ed841..c424956c 100644 --- a/config.yaml +++ b/config.yaml @@ -47,3 +47,12 @@ options: . This network will be used for tenant network traffic in overlay 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) + diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index f6305a7c..06a6bbec 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -6,12 +6,17 @@ from charmhelpers.core.hookenv import ( config, unit_get, ) +from charmhelpers.contrib.network.ip import ( + get_address_in_network, + get_ipv4_addr, + get_ipv6_addr, + is_bridge_member, +) from charmhelpers.core.host import list_nics, get_nic_hwaddr from charmhelpers.contrib.openstack import context from charmhelpers.core.host import service_running, service_start from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port from charmhelpers.contrib.openstack.utils import get_host_ip -from charmhelpers.contrib.network.ip import get_address_in_network from charmhelpers.contrib.openstack.context import OSContextGenerator import re @@ -139,3 +144,45 @@ class L3AgentContext(OSContextGenerator): else: ctxt['agent_mode'] = 'legacy' return ctxt + + +class NeutronPortContext(OSContextGenerator): + + def _resolve_port(self, config_key): + if not config(config_key): + return None + hwaddr_to_nic = {} + hwaddr_to_ip = {} + for nic in list_nics(['eth', 'bond']): + hwaddr = get_nic_hwaddr(nic) + hwaddr_to_nic[hwaddr] = nic + addresses = get_ipv4_addr(nic, fatal=False) + \ + get_ipv6_addr(iface=nic, fatal=False) + hwaddr_to_ip[hwaddr] = addresses + mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) + for entry in config(config_key).split(): + entry = entry.strip() + if re.match(mac_regex, entry): + if entry in hwaddr_to_nic and len(hwaddr_to_ip[entry]) == 0: + # If the nic is part of a bridge then don't use it + if is_bridge_member(hwaddr_to_nic[entry]): + continue + # Entry is a MAC address for a valid interface that doesn't + # have an IP address assigned yet. + return hwaddr_to_nic[entry] + else: + # If the passed entry is not a MAC address, assume it's a valid + # interface, and that the user put it there on purpose (we can + # trust it to be the real external network). + return entry + return None + + +class ExternalPortContext(NeutronPortContext): + + def __call__(self): + port = self._resolve_port('ext-port') + if port: + return {"ext_port": port} + else: + return None diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index c0381911..b3377cc2 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -7,6 +7,10 @@ from charmhelpers.contrib.openstack.utils import ( os_release, ) import neutron_ovs_context +from charmhelpers.contrib.network.ovs import ( + add_bridge, + add_bridge_port, +) NOVA_CONF_DIR = "/etc/nova" NEUTRON_CONF_DIR = "/etc/neutron" @@ -15,6 +19,7 @@ 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 +EXT_PORT_CONF = '/etc/init/ext-port.conf' BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { @@ -34,8 +39,15 @@ BASE_RESOURCE_MAP = OrderedDict([ 'services': ['neutron-vpn-agent'], 'contexts': [neutron_ovs_context.L3AgentContext()], }), + (EXT_PORT_CONF, { + 'services': [], + 'contexts': [neutron_ovs_context.ExternalPortContext()], + }), ]) TEMPLATES = 'templates/' +INT_BRIDGE = "br-int" +EXT_BRIDGE = "br-ex" +DATA_BRIDGE = 'br-data' def determine_dvr_packages(): @@ -77,3 +89,13 @@ def restart_map(): state. ''' return {k: v['services'] for k, v in resource_map().iteritems()} + + +def configure_ovs(): + add_bridge(INT_BRIDGE) + add_bridge(EXT_BRIDGE) + ext_port_ctx = neutron_ovs_context.ExternalPortContext()() + if ext_port_ctx and ext_port_ctx['ext_port']: + add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port']) + + add_bridge(DATA_BRIDGE) diff --git a/templates/ext-port.conf b/templates/ext-port.conf new file mode 100644 index 00000000..6080c30e --- /dev/null +++ b/templates/ext-port.conf @@ -0,0 +1,9 @@ +description "Enabling Quantum external networking port" + +start on runlevel [2345] + +task + +script + ip link set {{ ext_port }} up +end script \ No newline at end of file From d89c19dd78ba247f67a2c2b0ae45db93ae8b6fbf Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 2 Feb 2015 15:28:26 +0000 Subject: [PATCH 04/27] Call ovs setup --- hooks/neutron_ovs_hooks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index ee314606..848229bf 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -19,6 +19,7 @@ from charmhelpers.fetch import ( ) from neutron_ovs_utils import ( + configure_ovs, determine_packages, determine_dvr_packages, register_configs, @@ -44,6 +45,7 @@ def install(): def config_changed(): if determine_dvr_packages(): apt_install(determine_dvr_packages(), fatal=True) + configure_ovs() CONFIGS.write_all() From 35afca213d3ff5979f3d66150e9053751e8cb5da Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 3 Feb 2015 13:35:41 +0000 Subject: [PATCH 05/27] Tmp disable configure_ovs --- hooks/neutron_ovs_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index 848229bf..a410cc4c 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -45,7 +45,7 @@ def install(): def config_changed(): if determine_dvr_packages(): apt_install(determine_dvr_packages(), fatal=True) - configure_ovs() + #configure_ovs() CONFIGS.write_all() From a0c85efdd404bf02c43af37daed102eb3bf7c3b8 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 3 Feb 2015 14:17:30 +0000 Subject: [PATCH 06/27] Reenable configure_ovs --- hooks/neutron_ovs_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index a410cc4c..848229bf 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -45,7 +45,7 @@ def install(): def config_changed(): if determine_dvr_packages(): apt_install(determine_dvr_packages(), fatal=True) - #configure_ovs() + configure_ovs() CONFIGS.write_all() From c02f501399abccf51164c8910a95c61477c5030e Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 4 Feb 2015 16:30:03 +0000 Subject: [PATCH 07/27] Add relation to nova-cc (as neutron-gateway has) to get ks service info --- .../neutron-network-service-relation-changed | 1 + hooks/neutron-network-service-relation-joined | 1 + hooks/neutron_ovs_context.py | 54 ++++++++++++++++++- hooks/neutron_ovs_hooks.py | 10 ++++ hooks/neutron_ovs_utils.py | 19 +++++-- .../quantum-network-service-relation-changed | 1 + metadata.yaml | 2 + templates/juno/metadata_agent.ini | 19 +++++++ 8 files changed, 103 insertions(+), 4 deletions(-) create mode 120000 hooks/neutron-network-service-relation-changed create mode 120000 hooks/neutron-network-service-relation-joined create mode 120000 hooks/quantum-network-service-relation-changed create mode 100644 templates/juno/metadata_agent.ini diff --git a/hooks/neutron-network-service-relation-changed b/hooks/neutron-network-service-relation-changed new file mode 120000 index 00000000..55aa8e52 --- /dev/null +++ b/hooks/neutron-network-service-relation-changed @@ -0,0 +1 @@ +neutron_ovs_hooks.py \ No newline at end of file diff --git a/hooks/neutron-network-service-relation-joined b/hooks/neutron-network-service-relation-joined new file mode 120000 index 00000000..55aa8e52 --- /dev/null +++ b/hooks/neutron-network-service-relation-joined @@ -0,0 +1 @@ +neutron_ovs_hooks.py \ No newline at end of file diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 06a6bbec..3ff887f3 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -1,4 +1,6 @@ import ast +import os +import uuid from charmhelpers.core.hookenv import ( relation_ids, related_units, @@ -12,12 +14,16 @@ from charmhelpers.contrib.network.ip import ( get_ipv6_addr, is_bridge_member, ) +from charmhelpers.contrib.openstack.ip import resolve_address from charmhelpers.core.host import list_nics, get_nic_hwaddr from charmhelpers.contrib.openstack import context from charmhelpers.core.host import service_running, service_start from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port from charmhelpers.contrib.openstack.utils import get_host_ip -from charmhelpers.contrib.openstack.context import OSContextGenerator +from charmhelpers.contrib.openstack.context import ( + OSContextGenerator, + context_complete, +) import re @@ -186,3 +192,49 @@ class ExternalPortContext(NeutronPortContext): return {"ext_port": port} else: return None + + +class NetworkServiceContext(OSContextGenerator): + interfaces = ['neutron-network-service'] + + def __call__(self): + for rid in relation_ids('neutron-network-service'): + for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) + ctxt = { + 'service_protocol': + rdata.get('service_protocol') or 'http', + 'keystone_host': rdata.get('keystone_host'), + 'service_port': rdata.get('service_port'), + 'region': rdata.get('region'), + 'service_tenant': rdata.get('service_tenant'), + 'service_username': rdata.get('service_username'), + 'service_password': rdata.get('service_password'), + } + if context_complete(ctxt): + return ctxt + + +class DVRSharedSecretContext(OSContextGenerator): + + def get_shared_secret(self): + secret = None + if not os.path.exists(self.SHARED_SECRET): + secret = str(uuid.uuid4()) + with open(self.SHARED_SECRET, 'w') as secret_file: + secret_file.write(secret) + else: + with open(self.SHARED_SECRET, 'r') as secret_file: + secret = secret_file.read().strip() + return secret + + def __call__(self): + self.SHARED_SECRET = "/etc/neutron/secret.txt" + if use_dvr(): + ctxt = { + 'shared_secret': self.get_shared_secret(), + 'local_ip': resolve_address(), + } + else: + ctxt = {} + return ctxt diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index 848229bf..348ab115 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -22,6 +22,7 @@ from neutron_ovs_utils import ( configure_ovs, determine_packages, determine_dvr_packages, + get_shared_secret, register_configs, restart_map, ) @@ -38,6 +39,7 @@ def install(): apt_install(pkg, fatal=True) +@hooks.hook('neutron-network-service-relation-changed') @hooks.hook('neutron-plugin-relation-changed') @hooks.hook('neutron-plugin-api-relation-changed') @hooks.hook('config-changed') @@ -49,6 +51,14 @@ def config_changed(): CONFIGS.write_all() +@hooks.hook('neutron-plugin-relation-joined') +def neutron_plugin_joined(relation_id=None): + rel_data = { + 'metadata-shared-secret': get_shared_secret() + } + relation_set(relation_id=relation_id, **rel_data) + + @hooks.hook('amqp-relation-joined') def amqp_joined(relation_id=None): relation_set(relation_id=relation_id, diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index b3377cc2..5c5040cf 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -20,15 +20,17 @@ 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 EXT_PORT_CONF = '/etc/init/ext-port.conf' +NEUTRON_METADATA_AGENT_CONF = "/etc/neutron/metadata_agent.ini" + BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { - 'services': ['neutron-plugin-openvswitch-agent'], + 'services': ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent', 'neutron-openvswitch-agent', 'neutron-vpn-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext(), context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR)], }), (ML2_CONF, { - 'services': ['neutron-plugin-openvswitch-agent'], + 'services': ['neutron-plugin-openvswitch-agent', 'neutron-openvswitch-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext()], }), (NEUTRON_L3_AGENT_CONF, { @@ -43,6 +45,11 @@ BASE_RESOURCE_MAP = OrderedDict([ 'services': [], 'contexts': [neutron_ovs_context.ExternalPortContext()], }), + (NEUTRON_METADATA_AGENT_CONF, { + 'services': ['neutron-metadata-agent'], + 'contexts': [neutron_ovs_context.DVRSharedSecretContext(), + neutron_ovs_context.NetworkServiceContext()], + }), ]) TEMPLATES = 'templates/' INT_BRIDGE = "br-int" @@ -53,7 +60,7 @@ DATA_BRIDGE = 'br-data' def determine_dvr_packages(): pkgs = [] if neutron_ovs_context.use_dvr(): - pkgs = 'neutron-vpn-agent' + pkgs = ['neutron-vpn-agent'] return pkgs @@ -99,3 +106,9 @@ def configure_ovs(): add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port']) add_bridge(DATA_BRIDGE) + + +def get_shared_secret(): + ctxt = neutron_ovs_context.DVRSharedSecretContext()() + if 'shared_secret' in ctxt: + return ctxt['shared_secret'] diff --git a/hooks/quantum-network-service-relation-changed b/hooks/quantum-network-service-relation-changed new file mode 120000 index 00000000..55aa8e52 --- /dev/null +++ b/hooks/quantum-network-service-relation-changed @@ -0,0 +1 @@ +neutron_ovs_hooks.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 0e840258..790cda80 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -20,6 +20,8 @@ provides: neutron-plugin: interface: neutron-plugin scope: container + neutron-network-service: + interface: quantum requires: amqp: interface: rabbitmq diff --git a/templates/juno/metadata_agent.ini b/templates/juno/metadata_agent.ini new file mode 100644 index 00000000..3062d697 --- /dev/null +++ b/templates/juno/metadata_agent.ini @@ -0,0 +1,19 @@ +############################################################################### +# [ 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 }}://{{ keystone_host }}:{{ service_port }}/v2.0 +auth_region = {{ region }} +admin_tenant_name = {{ service_tenant }} +admin_user = {{ service_username }} +admin_password = {{ service_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 From b9fd91de35573b8e1a2cc26201ef42438cb37298 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 5 Feb 2015 09:57:47 +0000 Subject: [PATCH 08/27] Fix BASE_RESOURCE_MAP --- hooks/neutron_ovs_hooks.py | 1 + hooks/neutron_ovs_utils.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index 348ab115..88b97189 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -46,6 +46,7 @@ def install(): @restart_on_change(restart_map()) def config_changed(): if determine_dvr_packages(): + apt_update() apt_install(determine_dvr_packages(), fatal=True) configure_ovs() CONFIGS.write_all() diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 5c5040cf..b5314b5d 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -25,12 +25,12 @@ NEUTRON_METADATA_AGENT_CONF = "/etc/neutron/metadata_agent.ini" BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { - 'services': ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent', 'neutron-openvswitch-agent', 'neutron-vpn-agent'], + 'services': ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent', 'neutron-vpn-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext(), context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR)], }), (ML2_CONF, { - 'services': ['neutron-plugin-openvswitch-agent', 'neutron-openvswitch-agent'], + 'services': ['neutron-plugin-openvswitch-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext()], }), (NEUTRON_L3_AGENT_CONF, { @@ -42,7 +42,7 @@ BASE_RESOURCE_MAP = OrderedDict([ 'contexts': [neutron_ovs_context.L3AgentContext()], }), (EXT_PORT_CONF, { - 'services': [], + 'services': ['neutron-vpn-agent'], 'contexts': [neutron_ovs_context.ExternalPortContext()], }), (NEUTRON_METADATA_AGENT_CONF, { From 63e451fbcfd4b10f70784578cd741f6635835796 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 20 Feb 2015 08:17:24 +0000 Subject: [PATCH 09/27] Inform nova-compute if dvr is enabled as it'll need to enable the metadata service --- hooks/neutron_ovs_hooks.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index 88b97189..8ce3d5d0 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -8,6 +8,7 @@ from charmhelpers.core.hookenv import ( config, log, relation_set, + relation_ids, ) from charmhelpers.core.host import ( @@ -41,7 +42,6 @@ def install(): @hooks.hook('neutron-network-service-relation-changed') @hooks.hook('neutron-plugin-relation-changed') -@hooks.hook('neutron-plugin-api-relation-changed') @hooks.hook('config-changed') @restart_on_change(restart_map()) def config_changed(): @@ -51,6 +51,17 @@ def config_changed(): configure_ovs() CONFIGS.write_all() +@hooks.hook('neutron-plugin-api-relation-changed') +@restart_on_change(restart_map()) +def neutron_plugin_api_changed(): + if determine_dvr_packages(): + apt_update() + apt_install(determine_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): From 02dca40b5e98a34f007da840c2500bd301bcf29a Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 20 Feb 2015 09:13:08 +0000 Subject: [PATCH 10/27] Fix bug stopping agent type being set correctly. neutron_api_settings['enable_dvr'] is now a boolean not a string --- hooks/neutron_ovs_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index f97991d2..60ed1e15 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -147,7 +147,7 @@ class L3AgentContext(OSContextGenerator): def __call__(self): neutron_api_settings = _neutron_api_settings() ctxt = {} - if neutron_api_settings['enable_dvr'] == 'True': + if neutron_api_settings['enable_dvr']: ctxt['agent_mode'] = 'dvr' else: ctxt['agent_mode'] = 'legacy' From 030f9b9c280c80a1ce3d8b1ea8c768c6ff9dbc85 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 24 Feb 2015 16:23:38 +0000 Subject: [PATCH 11/27] Added unit tests --- unit_tests/test_neutron_ovs_context.py | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py index 631b37c1..80bd363d 100644 --- a/unit_tests/test_neutron_ovs_context.py +++ b/unit_tests/test_neutron_ovs_context.py @@ -90,6 +90,7 @@ class OVSPluginContextTest(CharmTestCase): self.relation_ids.return_value = ['rid2'] self.test_relation.set({'neutron-security-groups': 'True', 'l2-population': 'True', + 'enable-dvr': 'True', 'overlay-network-type': 'gre', }) self.get_host_ip.return_value = '127.0.0.15' @@ -98,7 +99,7 @@ class OVSPluginContextTest(CharmTestCase): expect = { 'neutron_alchemy_flags': {}, 'neutron_security_groups': True, - 'distributed_routing': False, + 'distributed_routing': True, 'verbose': True, 'local_ip': '127.0.0.15', 'config': 'neutron.randomconfig', @@ -167,3 +168,31 @@ class OVSPluginContextTest(CharmTestCase): } self.assertEquals(expect, napi_ctxt()) self.service_start.assertCalled() + +class L3AgentContextTest(CharmTestCase): + + def setUp(self): + super(L3AgentContextTest, self).setUp(context, TO_PATCH) + self.relation_get.side_effect = self.test_relation.get + self.config.side_effect = self.test_config.get + + def tearDown(self): + super(L3AgentContextTest, self).tearDown() + + def test_dvr_enabled(self): + self.related_units.return_value = ['unit1'] + self.relation_ids.return_value = ['rid2'] + self.test_relation.set({'neutron-security-groups': 'True', + 'enable-dvr': 'True', + 'l2-population': 'True', + 'overlay-network-type': 'vxlan'}) + self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'dvr'}) + + def test_dvr_disabled(self): + self.related_units.return_value = ['unit1'] + self.relation_ids.return_value = ['rid2'] + self.test_relation.set({'neutron-security-groups': 'True', + 'enable-dvr': 'False', + 'l2-population': 'True', + 'overlay-network-type': 'vxlan'}) + self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'legacy'}) From f5321e3f5076388b18551b7844a6113b7716d58b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Feb 2015 07:54:14 +0000 Subject: [PATCH 12/27] Use charm-helpers ExternalPortContext --- charm-helpers-sync.yaml | 2 +- .../charmhelpers/contrib/hahelpers/cluster.py | 6 +- .../charmhelpers/contrib/openstack/context.py | 96 +++++++++++++++++-- .../contrib/openstack/files/__init__.py | 18 ++++ .../contrib/openstack/files/check_haproxy.sh | 32 +++++++ .../files/check_haproxy_queue_depth.sh | 30 ++++++ hooks/charmhelpers/contrib/openstack/ip.py | 37 +++++++ .../contrib/openstack/templates/zeromq | 14 +++ hooks/charmhelpers/contrib/openstack/utils.py | 1 + hooks/charmhelpers/core/fstab.py | 4 +- hooks/neutron_ovs_context.py | 42 -------- hooks/neutron_ovs_utils.py | 2 +- 12 files changed, 228 insertions(+), 56 deletions(-) create mode 100644 hooks/charmhelpers/contrib/openstack/files/__init__.py create mode 100755 hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh create mode 100755 hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh create mode 100644 hooks/charmhelpers/contrib/openstack/templates/zeromq diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml index 9b5e79e9..35c175f4 100644 --- a/charm-helpers-sync.yaml +++ b/charm-helpers-sync.yaml @@ -1,4 +1,4 @@ -branch: lp:charm-helpers +branch: lp:~gnuoy/charm-helpers/neutron-contexts destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index 9a2588b6..9333efc3 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -48,6 +48,9 @@ from charmhelpers.core.hookenv import ( from charmhelpers.core.decorators import ( retry_on_exception, ) +from charmhelpers.core.strutils import ( + bool_from_string, +) class HAIncompleteConfig(Exception): @@ -164,7 +167,8 @@ def https(): . returns: boolean ''' - if config_get('use-https') == "yes": + use_https = config_get('use-https') + if use_https and bool_from_string(use_https): return True if config_get('ssl_cert') and config_get('ssl_key'): return True diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index c7c4cd4a..9385f1a2 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -19,9 +19,14 @@ import os import time from base64 import b64decode from subprocess import check_call +import re import six +from charmhelpers.core.host import ( + list_nics, + get_nic_hwaddr +) from charmhelpers.fetch import ( apt_install, filter_installed_packages, @@ -66,10 +71,12 @@ from charmhelpers.contrib.openstack.neutron import ( ) from charmhelpers.contrib.network.ip import ( get_address_in_network, + get_ipv4_addr, get_ipv6_addr, get_netmask_for_address, format_ipv6_addr, is_address_in_network, + is_bridge_member, ) from charmhelpers.contrib.openstack.utils import get_host_ip @@ -279,9 +286,25 @@ def db_ssl(rdata, ctxt, ssl_dir): class IdentityServiceContext(OSContextGenerator): interfaces = ['identity-service'] + def __init__(self, service=None, service_user=None): + self.service = service + self.service_user = service_user + def __call__(self): log('Generating template context for identity-service', level=DEBUG) ctxt = {} + + if self.service and self.service_user: + # This is required for pki token signing if we don't want /tmp to + # be used. + cachedir = '/var/cache/%s' % (self.service) + if not os.path.isdir(cachedir): + log("Creating service cache dir %s" % (cachedir), level=DEBUG) + mkdir(path=cachedir, owner=self.service_user, + group=self.service_user, perms=0o700) + + ctxt['signing_dir'] = cachedir + for rid in relation_ids('identity-service'): for unit in related_units(rid): rdata = relation_get(rid=rid, unit=unit) @@ -291,15 +314,16 @@ class IdentityServiceContext(OSContextGenerator): auth_host = format_ipv6_addr(auth_host) or auth_host svc_protocol = rdata.get('service_protocol') or 'http' auth_protocol = rdata.get('auth_protocol') or 'http' - ctxt = {'service_port': rdata.get('service_port'), - 'service_host': serv_host, - 'auth_host': auth_host, - 'auth_port': rdata.get('auth_port'), - 'admin_tenant_name': rdata.get('service_tenant'), - 'admin_user': rdata.get('service_username'), - 'admin_password': rdata.get('service_password'), - 'service_protocol': svc_protocol, - 'auth_protocol': auth_protocol} + ctxt.update({'service_port': rdata.get('service_port'), + 'service_host': serv_host, + 'auth_host': auth_host, + 'auth_port': rdata.get('auth_port'), + 'admin_tenant_name': rdata.get('service_tenant'), + 'admin_user': rdata.get('service_username'), + 'admin_password': rdata.get('service_password'), + 'service_protocol': svc_protocol, + 'auth_protocol': auth_protocol}) + if context_complete(ctxt): # NOTE(jamespage) this is required for >= icehouse # so a missing value just indicates keystone needs @@ -1021,6 +1045,8 @@ class ZeroMQContext(OSContextGenerator): for unit in related_units(rid): ctxt['zmq_nonce'] = relation_get('nonce', unit, rid) ctxt['zmq_host'] = relation_get('host', unit, rid) + ctxt['zmq_redis_address'] = relation_get( + 'zmq_redis_address', unit, rid) return ctxt @@ -1052,3 +1078,55 @@ class SysctlContext(OSContextGenerator): sysctl_create(sysctl_dict, '/etc/sysctl.d/50-{0}.conf'.format(charm_name())) return {'sysctl': sysctl_dict} + + +class NeutronPortContext(OSContextGenerator): + + def _resolve_port(self, config_key): + if not config(config_key): + return None + hwaddr_to_nic = {} + hwaddr_to_ip = {} + for nic in list_nics(['eth', 'bond']): + hwaddr = get_nic_hwaddr(nic) + hwaddr_to_nic[hwaddr] = nic + addresses = get_ipv4_addr(nic, fatal=False) + \ + get_ipv6_addr(iface=nic, fatal=False) + hwaddr_to_ip[hwaddr] = addresses + mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) + for entry in config(config_key).split(): + entry = entry.strip() + if re.match(mac_regex, entry): + if entry in hwaddr_to_nic and len(hwaddr_to_ip[entry]) == 0: + # If the nic is part of a bridge then don't use it + if is_bridge_member(hwaddr_to_nic[entry]): + continue + # Entry is a MAC address for a valid interface that doesn't + # have an IP address assigned yet. + return hwaddr_to_nic[entry] + else: + # If the passed entry is not a MAC address, assume it's a valid + # interface, and that the user put it there on purpose (we can + # trust it to be the real external network). + return entry + return None + + +class ExternalPortContext(NeutronPortContext): + + def __call__(self): + port = self._resolve_port('ext-port') + if port: + return {"ext_port": port} + else: + return None + + +class DataPortContext(NeutronPortContext): + + def __call__(self): + port = self._resolve_port('data-port') + if port: + return {"data_port": port} + else: + return None diff --git a/hooks/charmhelpers/contrib/openstack/files/__init__.py b/hooks/charmhelpers/contrib/openstack/files/__init__.py new file mode 100644 index 00000000..75876796 --- /dev/null +++ b/hooks/charmhelpers/contrib/openstack/files/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2014-2015 Canonical Limited. +# +# This file is part of charm-helpers. +# +# charm-helpers is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 as +# published by the Free Software Foundation. +# +# charm-helpers is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with charm-helpers. If not, see . + +# dummy __init__.py to fool syncer into thinking this is a syncable python +# module diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh b/hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh new file mode 100755 index 00000000..eb8527f5 --- /dev/null +++ b/hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh @@ -0,0 +1,32 @@ +#!/bin/bash +#-------------------------------------------- +# This file is managed by Juju +#-------------------------------------------- +# +# Copyright 2009,2012 Canonical Ltd. +# Author: Tom Haddon + +CRITICAL=0 +NOTACTIVE='' +LOGFILE=/var/log/nagios/check_haproxy.log +AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') + +for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'}); +do + output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK') + if [ $? != 0 ]; then + date >> $LOGFILE + echo $output >> $LOGFILE + /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1 + CRITICAL=1 + NOTACTIVE="${NOTACTIVE} $appserver" + fi +done + +if [ $CRITICAL = 1 ]; then + echo "CRITICAL:${NOTACTIVE}" + exit 2 +fi + +echo "OK: All haproxy instances looking good" +exit 0 diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh b/hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh new file mode 100755 index 00000000..3ebb5329 --- /dev/null +++ b/hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh @@ -0,0 +1,30 @@ +#!/bin/bash +#-------------------------------------------- +# This file is managed by Juju +#-------------------------------------------- +# +# Copyright 2009,2012 Canonical Ltd. +# Author: Tom Haddon + +# These should be config options at some stage +CURRQthrsh=0 +MAXQthrsh=100 + +AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') + +HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v) + +for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}') +do + CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3) + MAXQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 4) + + if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then + echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ" + exit 2 + fi +done + +echo "OK: All haproxy queue depths looking good" +exit 0 + diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/hooks/charmhelpers/contrib/openstack/ip.py index 9eabed73..29bbddcb 100644 --- a/hooks/charmhelpers/contrib/openstack/ip.py +++ b/hooks/charmhelpers/contrib/openstack/ip.py @@ -26,6 +26,8 @@ from charmhelpers.contrib.network.ip import ( ) from charmhelpers.contrib.hahelpers.cluster import is_clustered +from functools import partial + PUBLIC = 'public' INTERNAL = 'int' ADMIN = 'admin' @@ -107,3 +109,38 @@ def resolve_address(endpoint_type=PUBLIC): "clustered=%s)" % (net_type, clustered)) return resolved_address + + +def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC, + override=None): + """Returns the correct endpoint URL to advertise to Keystone. + + This method provides the correct endpoint URL which should be advertised to + the keystone charm for endpoint creation. This method allows for the url to + be overridden to force a keystone endpoint to have specific URL for any of + the defined scopes (admin, internal, public). + + :param configs: OSTemplateRenderer config templating object to inspect + for a complete https context. + :param url_template: str format string for creating the url template. Only + two values will be passed - the scheme+hostname + returned by the canonical_url and the port. + :param endpoint_type: str endpoint type to resolve. + :param override: str the name of the config option which overrides the + endpoint URL defined by the charm itself. None will + disable any overrides (default). + """ + if override: + # Return any user-defined overrides for the keystone endpoint URL. + user_value = config(override) + if user_value: + return user_value.strip() + + return url_template % (canonical_url(configs, endpoint_type), port) + + +public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC) + +internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL) + +admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN) diff --git a/hooks/charmhelpers/contrib/openstack/templates/zeromq b/hooks/charmhelpers/contrib/openstack/templates/zeromq new file mode 100644 index 00000000..0695eef1 --- /dev/null +++ b/hooks/charmhelpers/contrib/openstack/templates/zeromq @@ -0,0 +1,14 @@ +{% if zmq_host -%} +# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }}) +rpc_backend = zmq +rpc_zmq_host = {{ zmq_host }} +{% if zmq_redis_address -%} +rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis +matchmaker_heartbeat_freq = 15 +matchmaker_heartbeat_ttl = 30 +[matchmaker_redis] +host = {{ zmq_redis_address }} +{% else -%} +rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_ring.MatchMakerRing +{% endif -%} +{% endif -%} diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 26259a03..af2b3596 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -103,6 +103,7 @@ SWIFT_CODENAMES = OrderedDict([ ('2.1.0', 'juno'), ('2.2.0', 'juno'), ('2.2.1', 'kilo'), + ('2.2.2', 'kilo'), ]) DEFAULT_LOOPBACK_SIZE = '5G' diff --git a/hooks/charmhelpers/core/fstab.py b/hooks/charmhelpers/core/fstab.py index 9cdcc886..3056fbac 100644 --- a/hooks/charmhelpers/core/fstab.py +++ b/hooks/charmhelpers/core/fstab.py @@ -77,7 +77,7 @@ class Fstab(io.FileIO): for line in self.readlines(): line = line.decode('us-ascii') try: - if line.strip() and not line.startswith("#"): + if line.strip() and not line.strip().startswith("#"): yield self._hydrate_entry(line) except ValueError: pass @@ -104,7 +104,7 @@ class Fstab(io.FileIO): found = False for index, line in enumerate(lines): - if not line.startswith("#"): + if line.strip() and not line.strip().startswith("#"): if self._hydrate_entry(line) == entry: found = True break diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 60ed1e15..df4cfdd3 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -154,48 +154,6 @@ class L3AgentContext(OSContextGenerator): return ctxt -class NeutronPortContext(OSContextGenerator): - - def _resolve_port(self, config_key): - if not config(config_key): - return None - hwaddr_to_nic = {} - hwaddr_to_ip = {} - for nic in list_nics(['eth', 'bond']): - hwaddr = get_nic_hwaddr(nic) - hwaddr_to_nic[hwaddr] = nic - addresses = get_ipv4_addr(nic, fatal=False) + \ - get_ipv6_addr(iface=nic, fatal=False) - hwaddr_to_ip[hwaddr] = addresses - mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) - for entry in config(config_key).split(): - entry = entry.strip() - if re.match(mac_regex, entry): - if entry in hwaddr_to_nic and len(hwaddr_to_ip[entry]) == 0: - # If the nic is part of a bridge then don't use it - if is_bridge_member(hwaddr_to_nic[entry]): - continue - # Entry is a MAC address for a valid interface that doesn't - # have an IP address assigned yet. - return hwaddr_to_nic[entry] - else: - # If the passed entry is not a MAC address, assume it's a valid - # interface, and that the user put it there on purpose (we can - # trust it to be the real external network). - return entry - return None - - -class ExternalPortContext(NeutronPortContext): - - def __call__(self): - port = self._resolve_port('ext-port') - if port: - return {"ext_port": port} - else: - return None - - class NetworkServiceContext(OSContextGenerator): interfaces = ['neutron-network-service'] diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index ec5168f9..1bac5f42 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -45,7 +45,7 @@ DVR_RESOURCE_MAP = OrderedDict([ }), (EXT_PORT_CONF, { 'services': ['neutron-vpn-agent'], - 'contexts': [neutron_ovs_context.ExternalPortContext()], + 'contexts': [context.ExternalPortContext()], }), (NEUTRON_METADATA_AGENT_CONF, { 'services': ['neutron-metadata-agent'], From 0990ac13b331c2c5e84e940f94816cfdb622387e Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Feb 2015 08:15:08 +0000 Subject: [PATCH 13/27] Fix ExternalPortContext location --- hooks/neutron_ovs_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 1bac5f42..f64d58ff 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -105,7 +105,7 @@ def restart_map(): def configure_ovs(): add_bridge(INT_BRIDGE) add_bridge(EXT_BRIDGE) - ext_port_ctx = neutron_ovs_context.ExternalPortContext()() + ext_port_ctx = context.ExternalPortContext()() if ext_port_ctx and ext_port_ctx['ext_port']: add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port']) From 8b5d14e345f58f35bbce5f231b64df17d4cc6ccd Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Feb 2015 13:50:38 +0000 Subject: [PATCH 14/27] More unit tests --- hooks/neutron_ovs_context.py | 32 +++++----- hooks/neutron_ovs_hooks.py | 2 + unit_tests/test_neutron_ovs_context.py | 81 ++++++++++++++++++++++++++ unit_tests/test_neutron_ovs_hooks.py | 36 ++++++++++++ unit_tests/test_neutron_ovs_utils.py | 44 +++++++++++++- 5 files changed, 178 insertions(+), 17 deletions(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index df4cfdd3..62bec11a 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -9,9 +9,6 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.contrib.network.ip import ( get_address_in_network, - get_ipv4_addr, - get_ipv6_addr, - is_bridge_member, ) from charmhelpers.contrib.openstack.ip import resolve_address from charmhelpers.core.host import list_nics, get_nic_hwaddr @@ -175,24 +172,27 @@ class NetworkServiceContext(OSContextGenerator): 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 get_shared_secret(self): - secret = None - if not os.path.exists(self.SHARED_SECRET): - secret = str(uuid.uuid4()) - with open(self.SHARED_SECRET, 'w') as secret_file: - secret_file.write(secret) - else: - with open(self.SHARED_SECRET, 'r') as secret_file: - secret = secret_file.read().strip() - return secret - def __call__(self): - self.SHARED_SECRET = "/etc/neutron/secret.txt" if use_dvr(): ctxt = { - 'shared_secret': self.get_shared_secret(), + 'shared_secret': get_shared_secret(), 'local_ip': resolve_address(), } else: diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index 8ce3d5d0..c7ed9a02 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -51,6 +51,7 @@ def config_changed(): configure_ovs() CONFIGS.write_all() + @hooks.hook('neutron-plugin-api-relation-changed') @restart_on_change(restart_map()) def neutron_plugin_api_changed(): @@ -63,6 +64,7 @@ def neutron_plugin_api_changed(): 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): rel_data = { diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py index 80bd363d..5de00d0f 100644 --- a/unit_tests/test_neutron_ovs_context.py +++ b/unit_tests/test_neutron_ovs_context.py @@ -1,5 +1,6 @@ from test_utils import CharmTestCase +from test_utils import patch_open from mock import patch import neutron_ovs_context as context import charmhelpers @@ -7,6 +8,7 @@ TO_PATCH = [ 'relation_get', 'relation_ids', 'related_units', + 'resolve_address', 'config', 'unit_get', 'add_bridge', @@ -169,6 +171,7 @@ class OVSPluginContextTest(CharmTestCase): self.assertEquals(expect, napi_ctxt()) self.service_start.assertCalled() + class L3AgentContextTest(CharmTestCase): def setUp(self): @@ -196,3 +199,81 @@ class L3AgentContextTest(CharmTestCase): 'l2-population': 'True', 'overlay-network-type': 'vxlan'}) self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'legacy'}) + + +class NetworkServiceContext(CharmTestCase): + + def setUp(self): + super(NetworkServiceContext, self).setUp(context, TO_PATCH) + self.relation_get.side_effect = self.test_relation.get + self.config.side_effect = self.test_config.get + + def tearDown(self): + super(NetworkServiceContext, self).tearDown() + + def test_network_svc_ctxt(self): + self.related_units.return_value = ['unit1'] + self.relation_ids.return_value = ['rid2'] + self.test_relation.set({'service_protocol': 'http', + 'keystone_host': '10.0.0.10', + 'service_port': '8080', + 'region': 'region1', + 'service_tenant': 'tenant', + 'service_username': 'bob', + 'service_password': 'reallyhardpass'}) + self.assertEquals(context.NetworkServiceContext()(), + {'service_protocol': 'http', + 'keystone_host': '10.0.0.10', + 'service_port': '8080', + 'region': 'region1', + 'service_tenant': 'tenant', + 'service_username': 'bob', + 'service_password': 'reallyhardpass'}) + + +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, 'use_dvr') + @patch.object(context, 'get_shared_secret') + def test_shared_secretcontext_dvr(self, _shared_secret, _use_dvr): + _shared_secret.return_value = 'secret_thing' + _use_dvr.return_value = True + 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, 'use_dvr') + @patch.object(context, 'get_shared_secret') + def test_shared_secretcontext_nodvr(self, _shared_secret, _use_dvr): + _shared_secret.return_value = 'secret_thing' + _use_dvr.return_value = False + self.resolve_address.return_value = '10.0.0.10' + self.assertEquals(context.DVRSharedSecretContext()(), {}) diff --git a/unit_tests/test_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py index ac6317bf..c4180a98 100644 --- a/unit_tests/test_neutron_ovs_hooks.py +++ b/unit_tests/test_neutron_ovs_hooks.py @@ -24,7 +24,10 @@ TO_PATCH = [ 'config', 'CONFIGS', 'determine_packages', + 'determine_dvr_packages', + 'get_shared_secret', 'log', + 'relation_ids', 'relation_set', 'configure_ovs', ] @@ -61,6 +64,39 @@ class NeutronOVSHooksTests(CharmTestCase): self.assertTrue(self.CONFIGS.write_all.called) self.configure_ovs.assert_called_with() + @patch.object(neutron_ovs_context, 'use_dvr') + def test_config_changed_dvr(self, _use_dvr): + _use_dvr.return_value = True + 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') + @patch.object(neutron_ovs_context, 'use_dvr') + def test_neutron_plugin_api(self, _use_dvr, _plugin_joined): + _use_dvr.return_value = False + 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): self._call_hook('amqp-relation-joined') self.relation_set.assert_called_with( diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index ceee90cb..6ed7f357 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -1,7 +1,8 @@ -from mock import MagicMock, patch +from mock import MagicMock, patch, call from collections import OrderedDict import charmhelpers.contrib.openstack.templating as templating +from charmhelpers.contrib.openstack import context templating.OSConfigRenderer = MagicMock() @@ -16,6 +17,8 @@ import charmhelpers.core.hookenv as hookenv TO_PATCH = [ + 'add_bridge', + 'add_bridge_port', 'os_release', 'neutron_plugin_attribute', ] @@ -39,6 +42,15 @@ def _mock_npa(plugin, attr, net_manager=None): 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): def setUp(self): @@ -83,8 +95,20 @@ class TestNeutronOVSUtils(CharmTestCase): def test_resource_map(self, _use_dvr): _use_dvr.return_value = False _map = nutils.resource_map() + svcs = ['neutron-plugin-openvswitch-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(neutron_ovs_context, '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-vpn-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(neutron_ovs_context, 'use_dvr') def test_restart_map(self, _use_dvr): @@ -99,3 +123,21 @@ class TestNeutronOVSUtils(CharmTestCase): for item in _restart_map: self.assertTrue(item in _restart_map) self.assertTrue(expect[item] == _restart_map[item]) + + @patch.object(context, 'ExternalPortContext') + def test_configure_ovs_ovs_ext_port(self, _ext_port_ctxt): + _ext_port_ctxt.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') From 7a4e0e1222f4dcadb0d2f556c5b3f76184ba604d Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 2 Mar 2015 14:56:11 +0000 Subject: [PATCH 15/27] Purge pkgs when not in dvr mode --- hooks/neutron_ovs_hooks.py | 13 +++++++++---- hooks/neutron_ovs_utils.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index c7ed9a02..c5201352 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -16,16 +16,18 @@ from charmhelpers.core.host import ( ) from charmhelpers.fetch import ( - apt_install, apt_update + apt_install, apt_update, apt_purge ) from neutron_ovs_utils import ( + DVR_PACKAGES, configure_ovs, determine_packages, determine_dvr_packages, get_shared_secret, register_configs, restart_map, + use_dvr, ) hooks = Hooks() @@ -55,9 +57,11 @@ def config_changed(): @hooks.hook('neutron-plugin-api-relation-changed') @restart_on_change(restart_map()) def neutron_plugin_api_changed(): - if determine_dvr_packages(): + if use_dvr(): apt_update() - apt_install(determine_dvr_packages(), fatal=True) + 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 @@ -67,8 +71,9 @@ def neutron_plugin_api_changed(): @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': get_shared_secret() + 'metadata-shared-secret': secret, } relation_set(relation_id=relation_id, **rel_data) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index f64d58ff..55a8c8d7 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -21,7 +21,7 @@ NEUTRON_FWAAS_CONF = "/etc/neutron/fwaas_driver.ini" 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-vpn-agent'] BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { @@ -60,10 +60,9 @@ DATA_BRIDGE = 'br-data' def determine_dvr_packages(): - pkgs = [] - if neutron_ovs_context.use_dvr(): - pkgs = ['neutron-vpn-agent'] - return pkgs + if use_dvr(): + return DVR_PACKAGES + return [] def determine_packages(): @@ -87,7 +86,7 @@ def resource_map(): hook execution. ''' resource_map = deepcopy(BASE_RESOURCE_MAP) - if neutron_ovs_context.use_dvr(): + if use_dvr(): resource_map.update(DVR_RESOURCE_MAP) dvr_services = ['neutron-metadata-agent', 'neutron-vpn-agent'] resource_map[NEUTRON_CONF]['services'] += dvr_services @@ -116,3 +115,6 @@ def get_shared_secret(): ctxt = neutron_ovs_context.DVRSharedSecretContext()() if 'shared_secret' in ctxt: return ctxt['shared_secret'] + +def use_dvr(): + return neutron_ovs_context.use_dvr() From 8534a357afc2d8ed263d4f85512c1a0e1c2e8c47 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 3 Mar 2015 11:12:19 +0000 Subject: [PATCH 16/27] Fix lint and unit tests --- hooks/neutron_ovs_utils.py | 1 + unit_tests/test_neutron_ovs_hooks.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 55a8c8d7..252656a0 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -116,5 +116,6 @@ def get_shared_secret(): if 'shared_secret' in ctxt: return ctxt['shared_secret'] + def use_dvr(): return neutron_ovs_context.use_dvr() diff --git a/unit_tests/test_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py index c4180a98..dfa7d29d 100644 --- a/unit_tests/test_neutron_ovs_hooks.py +++ b/unit_tests/test_neutron_ovs_hooks.py @@ -21,6 +21,7 @@ utils.restart_map = _map TO_PATCH = [ 'apt_update', 'apt_install', + 'apt_purge', 'config', 'CONFIGS', 'determine_packages', @@ -30,6 +31,7 @@ TO_PATCH = [ 'relation_ids', 'relation_set', 'configure_ovs', + 'use_dvr', ] NEUTRON_CONF_DIR = "/etc/neutron" From b54c200370509c96d114bdf85531271c226c3262 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Mar 2015 08:37:29 +0000 Subject: [PATCH 17/27] Prep for merging next in --- charm-helpers-sync.yaml | 2 +- hooks/charmhelpers/contrib/network/ip.py | 85 +++++- .../charmhelpers/contrib/openstack/context.py | 267 +++++++++++++++--- .../charmhelpers/contrib/openstack/neutron.py | 70 +++++ hooks/charmhelpers/contrib/openstack/utils.py | 78 +---- hooks/charmhelpers/core/hookenv.py | 26 ++ hooks/charmhelpers/core/host.py | 6 +- hooks/charmhelpers/core/services/helpers.py | 16 +- hooks/neutron_ovs_context.py | 78 +---- hooks/neutron_ovs_utils.py | 4 +- 10 files changed, 433 insertions(+), 199 deletions(-) diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml index 35c175f4..c4fc1cb1 100644 --- a/charm-helpers-sync.yaml +++ b/charm-helpers-sync.yaml @@ -1,4 +1,4 @@ -branch: lp:~gnuoy/charm-helpers/neutron-contexts +branch: lp:~gnuoy/charm-helpers/neutron-shuffle destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index 98b17544..fff6d5ca 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -17,13 +17,16 @@ import glob import re import subprocess +import six +import socket from functools import partial from charmhelpers.core.hookenv import unit_get from charmhelpers.fetch import apt_install from charmhelpers.core.hookenv import ( - log + log, + WARNING, ) try: @@ -365,3 +368,83 @@ def is_bridge_member(nic): return True return False + + +def is_ip(address): + """ + Returns True if address is a valid IP address. + """ + try: + # Test to see if already an IPv4 address + socket.inet_aton(address) + return True + except socket.error: + return False + + +def ns_query(address): + try: + import dns.resolver + except ImportError: + apt_install('python-dnspython') + import dns.resolver + + if isinstance(address, dns.name.Name): + rtype = 'PTR' + elif isinstance(address, six.string_types): + rtype = 'A' + else: + return None + + answers = dns.resolver.query(address, rtype) + if answers: + return str(answers[0]) + return None + + +def get_host_ip(hostname, fallback=None): + """ + Resolves the IP for a given hostname, or returns + the input if it is already an IP. + """ + if is_ip(hostname): + return hostname + + ip_addr = ns_query(hostname) + if not ip_addr: + try: + ip_addr = socket.gethostbyname(hostname) + except: + log("Failed to resolve hostname '%s'" % (hostname), + level=WARNING) + return fallback + return ip_addr + + +def get_hostname(address, fqdn=True): + """ + Resolves hostname for given IP, or returns the input + if it is already a hostname. + """ + if is_ip(address): + try: + import dns.reversename + except ImportError: + apt_install("python-dnspython") + import dns.reversename + + rev = dns.reversename.from_address(address) + result = ns_query(rev) + if not result: + return None + else: + result = address + + if fqdn: + # strip trailing . + if result.endswith('.'): + return result[:-1] + else: + return result + else: + return result.split('.')[0] diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 9385f1a2..45e65790 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -16,17 +16,14 @@ import json import os +import re import time from base64 import b64decode from subprocess import check_call -import re import six +import yaml -from charmhelpers.core.host import ( - list_nics, - get_nic_hwaddr -) from charmhelpers.fetch import ( apt_install, filter_installed_packages, @@ -50,8 +47,11 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.core.sysctl import create as sysctl_create +from charmhelpers.core.strutils import bool_from_string from charmhelpers.core.host import ( + list_nics, + get_nic_hwaddr, mkdir, write_file, ) @@ -68,6 +68,11 @@ from charmhelpers.contrib.hahelpers.apache import ( ) from charmhelpers.contrib.openstack.neutron import ( neutron_plugin_attribute, + parse_data_port_mappings, +) +from charmhelpers.contrib.openstack.ip import ( + resolve_address, + INTERNAL, ) from charmhelpers.contrib.network.ip import ( get_address_in_network, @@ -79,7 +84,6 @@ from charmhelpers.contrib.network.ip import ( is_bridge_member, ) from charmhelpers.contrib.openstack.utils import get_host_ip - CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' ADDRESS_TYPES = ['admin', 'internal', 'public'] @@ -111,9 +115,41 @@ def context_complete(ctxt): def config_flags_parser(config_flags): """Parses config flags string into dict. + This parsing method supports a few different formats for the config + flag values to be parsed: + + 1. A string in the simple format of key=value pairs, with the possibility + of specifying multiple key value pairs within the same string. For + example, a string in the format of 'key1=value1, key2=value2' will + return a dict of: + {'key1': 'value1', + 'key2': 'value2'}. + + 2. A string in the above format, but supporting a comma-delimited list + of values for the same key. For example, a string in the format of + 'key1=value1, key2=value3,value4,value5' will return a dict of: + {'key1', 'value1', + 'key2', 'value2,value3,value4'} + + 3. A string containing a colon character (:) prior to an equal + character (=) will be treated as yaml and parsed as such. This can be + used to specify more complex key value pairs. For example, + a string in the format of 'key1: subkey1=value1, subkey2=value2' will + return a dict of: + {'key1', 'subkey1=value1, subkey2=value2'} + The provided config_flags string may be a list of comma-separated values which themselves may be comma-separated list of values. """ + # If we find a colon before an equals sign then treat it as yaml. + # Note: limit it to finding the colon first since this indicates assignment + # for inline yaml. + colon = config_flags.find(':') + equals = config_flags.find('=') + if colon > 0: + if colon < equals or equals < 0: + return yaml.safe_load(config_flags) + if config_flags.find('==') >= 0: log("config_flags is not in expected format (key=value)", level=ERROR) raise OSContextError @@ -198,7 +234,7 @@ class SharedDBContext(OSContextGenerator): unit=local_unit()) if set_hostname != access_hostname: relation_set(relation_settings={hostname_key: access_hostname}) - return ctxt # Defer any further hook execution for now.... + return None # Defer any further hook execution for now.... password_setting = 'password' if self.relation_prefix: @@ -701,7 +737,14 @@ class ApacheSSLContext(OSContextGenerator): 'endpoints': [], 'ext_ports': []} - for cn in self.canonical_names(): + cns = self.canonical_names() + if cns: + for cn in cns: + self.configure_cert(cn) + else: + # Expect cert/key provided in config (currently assumed that ca + # uses ip for cn) + cn = resolve_address(endpoint_type=INTERNAL) self.configure_cert(cn) addresses = self.get_network_addresses() @@ -857,6 +900,48 @@ class NeutronContext(OSContextGenerator): return ctxt +class NeutronPortContext(OSContextGenerator): + NIC_PREFIXES = ['eth', 'bond'] + + def resolve_ports(self, ports): + """Resolve NICs not yet bound to bridge(s) + + If hwaddress provided then returns resolved hwaddress otherwise NIC. + """ + if not ports: + return None + + hwaddr_to_nic = {} + hwaddr_to_ip = {} + for nic in list_nics(self.NIC_PREFIXES): + hwaddr = get_nic_hwaddr(nic) + hwaddr_to_nic[hwaddr] = nic + addresses = get_ipv4_addr(nic, fatal=False) + addresses += get_ipv6_addr(iface=nic, fatal=False) + hwaddr_to_ip[hwaddr] = addresses + + resolved = [] + mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) + for entry in ports: + if re.match(mac_regex, entry): + # NIC is in known NICs and does NOT hace an IP address + if entry in hwaddr_to_nic and not hwaddr_to_ip[entry]: + # If the nic is part of a bridge then don't use it + if is_bridge_member(hwaddr_to_nic[entry]): + continue + + # Entry is a MAC address for a valid interface that doesn't + # have an IP address assigned yet. + resolved.append(hwaddr_to_nic[entry]) + else: + # If the passed entry is not a MAC address, assume it's a valid + # interface, and that the user put it there on purpose (we can + # trust it to be the real external network). + resolved.append(entry) + + return resolved + + class OSConfigFlagContext(OSContextGenerator): """Provides support for user-defined config flags. @@ -1080,53 +1165,143 @@ class SysctlContext(OSContextGenerator): return {'sysctl': sysctl_dict} -class NeutronPortContext(OSContextGenerator): +class NeutronAPIContext(OSContextGenerator): + ''' + Inspects current neutron-plugin-api relation for neutron settings. Return + defaults if it is not present. + ''' + interfaces = ['neutron-plugin-api'] - def _resolve_port(self, config_key): - if not config(config_key): - return None - hwaddr_to_nic = {} - hwaddr_to_ip = {} - for nic in list_nics(['eth', 'bond']): - hwaddr = get_nic_hwaddr(nic) - hwaddr_to_nic[hwaddr] = nic - addresses = get_ipv4_addr(nic, fatal=False) + \ - get_ipv6_addr(iface=nic, fatal=False) - hwaddr_to_ip[hwaddr] = addresses - mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) - for entry in config(config_key).split(): - entry = entry.strip() - if re.match(mac_regex, entry): - if entry in hwaddr_to_nic and len(hwaddr_to_ip[entry]) == 0: - # If the nic is part of a bridge then don't use it - if is_bridge_member(hwaddr_to_nic[entry]): - continue - # Entry is a MAC address for a valid interface that doesn't - # have an IP address assigned yet. - return hwaddr_to_nic[entry] + def __call__(self): + self.neutron_defaults = { + 'l2_population': { + 'rel_key': 'l2-population', + 'default': False, + }, + 'overlay_network_type': { + 'rel_key': 'overlay-network-type', + 'default': 'gre', + }, + 'neutron_security_groups': { + 'rel_key': 'neutron-security-groups', + 'default': False, + }, + 'network_device_mtu': { + 'rel_key': 'network-device-mtu', + 'default': None, + }, + 'enable_dvr': { + 'rel_key': 'enable-dvr', + 'default': False, + }, + 'enable_l3ha': { + 'rel_key': 'enable-l3ha', + 'default': False, + }, + } + ctxt = self.get_neutron_options({}) + for rid in relation_ids('neutron-plugin-api'): + for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) + if 'l2-population' in rdata: + ctxt.update(self.get_neutron_options(rdata)) + + return ctxt + + def get_neutron_options(self, rdata): + settings = {} + for nkey in self.neutron_defaults.keys(): + defv = self.neutron_defaults[nkey]['default'] + rkey = self.neutron_defaults[nkey]['rel_key'] + if rkey in rdata.keys(): + if type(defv) is bool: + settings[nkey] = bool_from_string(rdata[rkey]) + else: + settings[nkey] = rdata[rkey] else: - # If the passed entry is not a MAC address, assume it's a valid - # interface, and that the user put it there on purpose (we can - # trust it to be the real external network). - return entry - return None + settings[nkey] = defv + return settings class ExternalPortContext(NeutronPortContext): def __call__(self): - port = self._resolve_port('ext-port') - if port: - return {"ext_port": port} - else: - return None + ctxt = {} + ports = config('ext-port') + if ports: + ports = [p.strip() for p in ports.split()] + ports = self.resolve_ports(ports) + if ports: + ctxt = {"ext_port": ports[0]} + napi_settings = NeutronAPIContext()() + mtu = napi_settings.get('network_device_mtu') + if mtu: + ctxt['ext_port_mtu'] = mtu + + return ctxt class DataPortContext(NeutronPortContext): def __call__(self): - port = self._resolve_port('data-port') - if port: - return {"data_port": port} - else: - return None + ports = config('data-port') + if ports: + portmap = parse_data_port_mappings(ports) + ports = portmap.values() + resolved = self.resolve_ports(ports) + normalized = {get_nic_hwaddr(port): port for port in resolved + if port not in ports} + normalized.update({port: port for port in resolved + if port in ports}) + if resolved: + return {bridge: normalized[port] for bridge, port in + six.iteritems(portmap) if port in normalized.keys()} + + return None + + +class PhyNICMTUContext(DataPortContext): + + def __call__(self): + ctxt = {} + mappings = super(PhyNICMTUContext, self).__call__() + if mappings and mappings.values(): + ports = mappings.values() + napi_settings = NeutronAPIContext()() + mtu = napi_settings.get('network_device_mtu') + if mtu: + ctxt["devs"] = '\\n'.join(ports) + ctxt['mtu'] = mtu + + return ctxt + + +class NetworkServiceContext(OSContextGenerator): + + def __init__(self, rel_name='quantum-network-service'): + self.rel_name = rel_name + self.interfaces = [rel_name] + + def __call__(self): + for rid in relation_ids(self.rel_name): + for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) + ctxt = { + 'keystone_host': rdata.get('keystone_host'), + 'service_port': rdata.get('service_port'), + 'auth_port': rdata.get('auth_port'), + 'service_tenant': rdata.get('service_tenant'), + 'service_username': rdata.get('service_username'), + 'service_password': rdata.get('service_password'), + 'quantum_host': rdata.get('quantum_host'), + 'quantum_port': rdata.get('quantum_port'), + 'quantum_url': rdata.get('quantum_url'), + 'region': rdata.get('region'), + 'service_protocol': + rdata.get('service_protocol') or 'http', + 'auth_protocol': + rdata.get('auth_protocol') or 'http', + } + if context_complete(ctxt): + return ctxt + return {} diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 902757fe..f8851050 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -16,6 +16,7 @@ # Various utilies for dealing with Neutron and the renaming from Quantum. +import six from subprocess import check_output from charmhelpers.core.hookenv import ( @@ -237,3 +238,72 @@ def network_manager(): else: # ensure accurate naming for all releases post-H return 'neutron' + + +def parse_mappings(mappings): + parsed = {} + if mappings: + mappings = mappings.split(' ') + for m in mappings: + p = m.partition(':') + if p[1] == ':': + parsed[p[0].strip()] = p[2].strip() + + return parsed + + +def parse_bridge_mappings(mappings): + """Parse bridge mappings. + + Mappings must be a space-delimited list of provider:bridge mappings. + + Returns dict of the form {provider:bridge}. + """ + return parse_mappings(mappings) + + +def parse_data_port_mappings(mappings, default_bridge='br-data'): + """Parse data port mappings. + + Mappings must be a space-delimited list of bridge:port mappings. + + Returns dict of the form {bridge:port}. + """ + _mappings = parse_mappings(mappings) + if not _mappings: + if not mappings: + return {} + + # For backwards-compatibility we need to support port-only provided in + # config. + _mappings = {default_bridge: mappings.split(' ')[0]} + + bridges = _mappings.keys() + ports = _mappings.values() + if len(set(bridges)) != len(bridges): + raise Exception("It is not allowed to have more than one port " + "configured on the same bridge") + + if len(set(ports)) != len(ports): + raise Exception("It is not allowed to have the same port configured " + "on more than one bridge") + + return _mappings + + +def parse_vlan_range_mappings(mappings): + """Parse vlan range mappings. + + Mappings must be a space-delimited list of provider:start:end mappings. + + Returns dict of the form {provider: (start, end)}. + """ + _mappings = parse_mappings(mappings) + if not _mappings: + return {} + + mappings = {} + for p, r in six.iteritems(_mappings): + mappings[p] = tuple(r.split(':')) + + return mappings diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index af2b3596..4f110c63 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -23,12 +23,13 @@ from functools import wraps import subprocess import json import os -import socket import sys import six import yaml +from charmhelpers.contrib.network import ip + from charmhelpers.core.hookenv import ( config, log as juju_log, @@ -421,77 +422,10 @@ def clean_storage(block_device): else: zap_disk(block_device) - -def is_ip(address): - """ - Returns True if address is a valid IP address. - """ - try: - # Test to see if already an IPv4 address - socket.inet_aton(address) - return True - except socket.error: - return False - - -def ns_query(address): - try: - import dns.resolver - except ImportError: - apt_install('python-dnspython') - import dns.resolver - - if isinstance(address, dns.name.Name): - rtype = 'PTR' - elif isinstance(address, six.string_types): - rtype = 'A' - else: - return None - - answers = dns.resolver.query(address, rtype) - if answers: - return str(answers[0]) - return None - - -def get_host_ip(hostname): - """ - Resolves the IP for a given hostname, or returns - the input if it is already an IP. - """ - if is_ip(hostname): - return hostname - - return ns_query(hostname) - - -def get_hostname(address, fqdn=True): - """ - Resolves hostname for given IP, or returns the input - if it is already a hostname. - """ - if is_ip(address): - try: - import dns.reversename - except ImportError: - apt_install('python-dnspython') - import dns.reversename - - rev = dns.reversename.from_address(address) - result = ns_query(rev) - if not result: - return None - else: - result = address - - if fqdn: - # strip trailing . - if result.endswith('.'): - return result[:-1] - else: - return result - else: - return result.split('.')[0] +is_ip = ip.is_ip +ns_query = ip.ns_query +get_host_ip = ip.get_host_ip +get_hostname = ip.get_hostname def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'): diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index cf552b39..715dd4c5 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -566,3 +566,29 @@ class Hooks(object): def charm_dir(): """Return the root directory of the current charm""" return os.environ.get('CHARM_DIR') + + +@cached +def action_get(key=None): + """Gets the value of an action parameter, or all key/value param pairs""" + cmd = ['action-get'] + if key is not None: + cmd.append(key) + cmd.append('--format=json') + action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8')) + return action_data + + +def action_set(values): + """Sets the values to be returned after the action finishes""" + cmd = ['action-set'] + for k, v in list(values.items()): + cmd.append('{}={}'.format(k, v)) + subprocess.check_call(cmd) + + +def action_fail(message): + """Sets the action status to failed and sets the error message. + + The results set by action_set are preserved.""" + subprocess.check_call(['action-fail', message]) diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index b771c611..830822af 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -339,12 +339,16 @@ def lsb_release(): def pwgen(length=None): """Generate a random pasword.""" if length is None: + # A random length is ok to use a weak PRNG length = random.choice(range(35, 45)) alphanumeric_chars = [ l for l in (string.ascii_letters + string.digits) if l not in 'l0QD1vAEIOUaeiou'] + # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the + # actual password + random_generator = random.SystemRandom() random_chars = [ - random.choice(alphanumeric_chars) for _ in range(length)] + random_generator.choice(alphanumeric_chars) for _ in range(length)] return(''.join(random_chars)) diff --git a/hooks/charmhelpers/core/services/helpers.py b/hooks/charmhelpers/core/services/helpers.py index 5e3af9da..3eb5fb44 100644 --- a/hooks/charmhelpers/core/services/helpers.py +++ b/hooks/charmhelpers/core/services/helpers.py @@ -45,12 +45,14 @@ class RelationContext(dict): """ name = None interface = None - required_keys = [] def __init__(self, name=None, additional_required_keys=None): + if not hasattr(self, 'required_keys'): + self.required_keys = [] + if name is not None: self.name = name - if additional_required_keys is not None: + if additional_required_keys: self.required_keys.extend(additional_required_keys) self.get_data() @@ -134,7 +136,10 @@ class MysqlRelation(RelationContext): """ name = 'db' interface = 'mysql' - required_keys = ['host', 'user', 'password', 'database'] + + def __init__(self, *args, **kwargs): + self.required_keys = ['host', 'user', 'password', 'database'] + RelationContext.__init__(self, *args, **kwargs) class HttpRelation(RelationContext): @@ -146,7 +151,10 @@ class HttpRelation(RelationContext): """ name = 'website' interface = 'http' - required_keys = ['host', 'port'] + + def __init__(self, *args, **kwargs): + self.required_keys = ['host', 'port'] + RelationContext.__init__(self, *args, **kwargs) def provide_data(self): return { diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 62bec11a..513c577b 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -1,9 +1,6 @@ import os import uuid from charmhelpers.core.hookenv import ( - relation_ids, - related_units, - relation_get, config, unit_get, ) @@ -12,14 +9,13 @@ from charmhelpers.contrib.network.ip import ( ) from charmhelpers.contrib.openstack.ip import resolve_address from charmhelpers.core.host import list_nics, get_nic_hwaddr -from charmhelpers.core.strutils import bool_from_string from charmhelpers.contrib.openstack import context from charmhelpers.core.host import service_running, service_start from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port from charmhelpers.contrib.openstack.utils import get_host_ip from charmhelpers.contrib.openstack.context import ( OSContextGenerator, - context_complete, + NeutronAPIContext, ) import re @@ -28,47 +24,6 @@ OVS_BRIDGE = 'br-int' DATA_BRIDGE = 'br-data' -def _neutron_api_settings(): - ''' - Inspects current neutron-plugin relation - ''' - neutron_settings = { - 'neutron_security_groups': False, - 'l2_population': True, - 'overlay_network_type': 'gre', - 'enable_dvr': False, - } - for rid in relation_ids('neutron-plugin-api'): - for unit in related_units(rid): - rdata = relation_get(rid=rid, unit=unit) - if 'l2-population' not in rdata: - continue - neutron_settings = { - 'l2_population': bool_from_string(rdata['l2-population']), - 'overlay_network_type': rdata['overlay-network-type'], - 'neutron_security_groups': bool_from_string( - rdata['neutron-security-groups'] - ), - } - if 'enable-dvr' in rdata: - neutron_settings['enable_dvr'] = bool_from_string( - rdata['enable-dvr'] - ) - # Override with configuration if set to true - if config('disable-security-groups'): - neutron_settings['neutron_security_groups'] = False - return neutron_settings - return neutron_settings - - -def use_dvr(): - api_settings = _neutron_api_settings() - if 'enable_dvr' in api_settings: - return api_settings['enable_dvr'] - else: - return False - - class OVSPluginContext(context.NeutronContext): interfaces = [] @@ -82,7 +37,7 @@ class OVSPluginContext(context.NeutronContext): @property def neutron_security_groups(self): - neutron_api_settings = _neutron_api_settings() + neutron_api_settings = NeutronAPIContext()() return neutron_api_settings['neutron_security_groups'] def get_data_port(self): @@ -125,10 +80,10 @@ class OVSPluginContext(context.NeutronContext): ovs_ctxt['local_ip'] = \ get_address_in_network(config('os-data-network'), get_host_ip(unit_get('private-address'))) - neutron_api_settings = _neutron_api_settings() + neutron_api_settings = NeutronAPIContext()() ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups ovs_ctxt['l2_population'] = neutron_api_settings['l2_population'] - ovs_ctxt['distributed_routing'] = use_dvr() + ovs_ctxt['distributed_routing'] = neutron_api_settings['enable_dvr'] ovs_ctxt['overlay_network_type'] = \ neutron_api_settings['overlay_network_type'] # TODO: We need to sort out the syslog and debug/verbose options as a @@ -142,7 +97,7 @@ class OVSPluginContext(context.NeutronContext): class L3AgentContext(OSContextGenerator): def __call__(self): - neutron_api_settings = _neutron_api_settings() + neutron_api_settings = NeutronAPIContext()() ctxt = {} if neutron_api_settings['enable_dvr']: ctxt['agent_mode'] = 'dvr' @@ -151,27 +106,6 @@ class L3AgentContext(OSContextGenerator): return ctxt -class NetworkServiceContext(OSContextGenerator): - interfaces = ['neutron-network-service'] - - def __call__(self): - for rid in relation_ids('neutron-network-service'): - for unit in related_units(rid): - rdata = relation_get(rid=rid, unit=unit) - ctxt = { - 'service_protocol': - rdata.get('service_protocol') or 'http', - 'keystone_host': rdata.get('keystone_host'), - 'service_port': rdata.get('service_port'), - 'region': rdata.get('region'), - 'service_tenant': rdata.get('service_tenant'), - 'service_username': rdata.get('service_username'), - 'service_password': rdata.get('service_password'), - } - if context_complete(ctxt): - return ctxt - - SHARED_SECRET = "/etc/neutron/secret.txt" @@ -190,7 +124,7 @@ def get_shared_secret(): class DVRSharedSecretContext(OSContextGenerator): def __call__(self): - if use_dvr(): + if NeutronAPIContext()()['enable_dvr']: ctxt = { 'shared_secret': get_shared_secret(), 'local_ip': resolve_address(), diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 252656a0..7ad866a3 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -50,7 +50,7 @@ DVR_RESOURCE_MAP = OrderedDict([ (NEUTRON_METADATA_AGENT_CONF, { 'services': ['neutron-metadata-agent'], 'contexts': [neutron_ovs_context.DVRSharedSecretContext(), - neutron_ovs_context.NetworkServiceContext()], + context.NetworkServiceContext()], }), ]) TEMPLATES = 'templates/' @@ -118,4 +118,4 @@ def get_shared_secret(): def use_dvr(): - return neutron_ovs_context.use_dvr() + return context.NeutronAPIContext()()['enable_dvr'] From 7f32b61062464c8c29fbcfeb36a3cd08ee5848c8 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Mar 2015 10:29:27 +0000 Subject: [PATCH 18/27] Fixed empty metadata info --- hooks/neutron_ovs_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index ee7435bd..8cdcb3c4 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -56,7 +56,9 @@ DVR_RESOURCE_MAP = OrderedDict([ (NEUTRON_METADATA_AGENT_CONF, { 'services': ['neutron-metadata-agent'], 'contexts': [neutron_ovs_context.DVRSharedSecretContext(), - context.NetworkServiceContext()], + context.NetworkServiceContext( + rel_name='neutron-network-service' + )], }), ]) TEMPLATES = 'templates/' From 0f1309df41afff67ae4c66c73bd6dfa8c5982633 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 25 Mar 2015 10:31:44 +0000 Subject: [PATCH 19/27] Fixed lint --- unit_tests/test_neutron_ovs_context.py | 9 ++++++--- unit_tests/test_neutron_ovs_hooks.py | 1 - unit_tests/test_neutron_ovs_utils.py | 10 +++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py index c0602396..0232aecb 100644 --- a/unit_tests/test_neutron_ovs_context.py +++ b/unit_tests/test_neutron_ovs_context.py @@ -16,6 +16,7 @@ TO_PATCH = [ 'get_host_ip', ] + def fake_context(settings): def outer(): def inner(): @@ -23,6 +24,7 @@ def fake_context(settings): return inner return outer + class OVSPluginContextTest(CharmTestCase): def setUp(self): @@ -262,7 +264,6 @@ class DVRSharedSecretContext(CharmTestCase): TO_PATCH) self.config.side_effect = self.test_config.get - @patch('os.path') @patch('uuid.uuid4') def test_secret_created_stored(self, _uuid4, _path): @@ -287,7 +288,8 @@ class DVRSharedSecretContext(CharmTestCase): @patch.object(context, 'NeutronAPIContext') @patch.object(context, 'get_shared_secret') - def test_shared_secretcontext_dvr(self, _shared_secret, _NeutronAPIContext): + def test_shared_secretcontext_dvr(self, _shared_secret, + _NeutronAPIContext): _NeutronAPIContext.side_effect = fake_context({'enable_dvr': True}) _shared_secret.return_value = 'secret_thing' #_use_dvr.return_value = True @@ -298,7 +300,8 @@ class DVRSharedSecretContext(CharmTestCase): @patch.object(context, 'NeutronAPIContext') @patch.object(context, 'get_shared_secret') - def test_shared_secretcontext_nodvr(self, _shared_secret, _NeutronAPIContext): + 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' diff --git a/unit_tests/test_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py index b167c886..d651d00f 100644 --- a/unit_tests/test_neutron_ovs_hooks.py +++ b/unit_tests/test_neutron_ovs_hooks.py @@ -1,7 +1,6 @@ from mock import MagicMock, patch, call from test_utils import CharmTestCase -import neutron_ovs_context with patch('charmhelpers.core.hookenv.config') as config: config.return_value = 'neutron' diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index 3ad82284..7532d21d 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -61,7 +61,7 @@ class TestNeutronOVSUtils(CharmTestCase): # Reset cached cache hookenv.cache = {} - @patch.object(nutils, 'use_dvr') + @patch.object(nutils, 'use_dvr') @patch.object(charmhelpers.contrib.openstack.neutron, 'os_release') @patch.object(charmhelpers.contrib.openstack.neutron, 'headers_package') def test_determine_packages(self, _head_pkgs, _os_rel, _use_dvr): @@ -72,7 +72,7 @@ class TestNeutronOVSUtils(CharmTestCase): expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]] self.assertItemsEqual(pkg_list, expect) - @patch.object(nutils, 'use_dvr') + @patch.object(nutils, 'use_dvr') def test_register_configs(self, _use_dvr): class _mock_OSConfigRenderer(): def __init__(self, templates_dir=None, openstack_release=None): @@ -92,7 +92,7 @@ class TestNeutronOVSUtils(CharmTestCase): '/etc/init/os-charm-phy-nic-mtu.conf'] self.assertItemsEqual(_regconfs.configs, confs) - @patch.object(nutils, 'use_dvr') + @patch.object(nutils, 'use_dvr') def test_resource_map(self, _use_dvr): _use_dvr.return_value = False _map = nutils.resource_map() @@ -101,7 +101,7 @@ class TestNeutronOVSUtils(CharmTestCase): [self.assertIn(q_conf, _map.keys()) for q_conf in confs] self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs) - @patch.object(nutils, 'use_dvr') + @patch.object(nutils, 'use_dvr') def test_resource_map_dvr(self, _use_dvr): _use_dvr.return_value = True _map = nutils.resource_map() @@ -111,7 +111,7 @@ class TestNeutronOVSUtils(CharmTestCase): [self.assertIn(q_conf, _map.keys()) for q_conf in confs] self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs) - @patch.object(nutils, 'use_dvr') + @patch.object(nutils, 'use_dvr') def test_restart_map(self, _use_dvr): _use_dvr.return_value = False _restart_map = nutils.restart_map() From f87b3d5a6d3b595258b0658bd55541550a10b931 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Mar 2015 10:00:52 +0000 Subject: [PATCH 20/27] Add mtu fixes to configure ovs and fix unit tests --- hooks/neutron_ovs_utils.py | 34 ++++++++++++- unit_tests/test_neutron_ovs_utils.py | 73 ++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 8cdcb3c4..e82c28c3 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -10,6 +10,21 @@ 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" @@ -110,13 +125,28 @@ def restart_map(): def configure_ovs(): + if not service_running('openvswitch-switch'): + full_restart() add_bridge(INT_BRIDGE) add_bridge(EXT_BRIDGE) - ext_port_ctx = context.ExternalPortContext()() + ext_port_ctx = ExternalPortContext()() if ext_port_ctx and ext_port_ctx['ext_port']: add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port']) - add_bridge(DATA_BRIDGE) + portmaps = DataPortContext()() + bridgemaps = parse_bridge_mappings(config('bridge-mappings')) + print bridgemaps + for provider, br in bridgemaps.iteritems(): + add_bridge(br) + print portmaps + 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(): diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index 7532d21d..f0e6bb6d 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -2,7 +2,6 @@ from mock import MagicMock, patch, call from collections import OrderedDict import charmhelpers.contrib.openstack.templating as templating -from charmhelpers.contrib.openstack import context templating.OSConfigRenderer = MagicMock() @@ -19,8 +18,13 @@ import charmhelpers.core.hookenv as hookenv TO_PATCH = [ 'add_bridge', 'add_bridge_port', + 'config', 'os_release', 'neutron_plugin_attribute', + 'full_restart', + 'service_running', + 'service_restart', + 'ExternalPortContext', ] head_pkg = 'linux-headers-3.15.0-5-generic' @@ -56,6 +60,7 @@ class TestNeutronOVSUtils(CharmTestCase): def setUp(self): super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH) self.neutron_plugin_attribute.side_effect = _mock_npa + self.config.side_effect = self.test_config.get def tearDown(self): # Reset cached cache @@ -126,9 +131,58 @@ class TestNeutronOVSUtils(CharmTestCase): self.assertTrue(item in _restart_map) self.assertTrue(expect[item] == _restart_map[item]) - @patch.object(context, 'ExternalPortContext') - def test_configure_ovs_ovs_ext_port(self, _ext_port_ctxt): - _ext_port_ctxt.return_value = \ + @patch('charmhelpers.contrib.openstack.context.config') + def test_configure_ovs_ovs_data_port(self, mock_config): + 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('charmhelpers.contrib.openstack.context.config') + def test_configure_ovs_starts_service_if_required(self, mock_config): + 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('charmhelpers.contrib.openstack.context.config') + def test_configure_ovs_doesnt_restart_service(self, mock_config): + 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('charmhelpers.contrib.openstack.context.config') + def test_configure_ovs_ovs_ext_port(self, mock_config): + 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([ @@ -137,6 +191,17 @@ class TestNeutronOVSUtils(CharmTestCase): call('br-data') ]) self.add_bridge_port.assert_called_with('br-ex', 'eth0') +# @patch.object(context, 'ExternalPortContext') +# def test_configure_ovs_ovs_ext_port(self, _ext_port_ctxt): +# _ext_port_ctxt.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): From c13cd7e0c8d69da205b8a35675687e0c5f778ea1 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Mar 2015 10:22:41 +0000 Subject: [PATCH 21/27] Stop managing the bridges in two places --- hooks/neutron_ovs_context.py | 19 ------------------- hooks/neutron_ovs_utils.py | 6 +++--- unit_tests/test_neutron_ovs_context.py | 20 -------------------- unit_tests/test_neutron_ovs_utils.py | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 46 deletions(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index a7192a4e..c5d4b1d1 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -44,23 +44,6 @@ class OVSPluginContext(context.NeutronContext): neutron_api_settings = NeutronAPIContext()() 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): # In addition to generating config context, ensure the OVS service @@ -70,8 +53,6 @@ class OVSPluginContext(context.NeutronContext): if not ovs_ctxt: return {} - self._ensure_bridge() - conf = config() ovs_ctxt['local_ip'] = \ get_address_in_network(config('os-data-network'), diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index e82c28c3..fccf9309 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -129,16 +129,16 @@ def configure_ovs(): full_restart() add_bridge(INT_BRIDGE) add_bridge(EXT_BRIDGE) - ext_port_ctx = ExternalPortContext()() + 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')) - print bridgemaps for provider, br in bridgemaps.iteritems(): add_bridge(br) - print portmaps if not portmaps or br not in portmaps: continue diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py index 0232aecb..93fab0ff 100644 --- a/unit_tests/test_neutron_ovs_context.py +++ b/unit_tests/test_neutron_ovs_context.py @@ -65,26 +65,6 @@ class OVSPluginContextTest(CharmTestCase): self.assertEquals(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_ids') @patch.object(charmhelpers.contrib.openstack.context, 'related_units') diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index f0e6bb6d..7eb798f7 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -131,8 +131,10 @@ class TestNeutronOVSUtils(CharmTestCase): self.assertTrue(item in _restart_map) 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): + 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 = \ @@ -161,24 +163,31 @@ class TestNeutronOVSUtils(CharmTestCase): # 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): + def test_configure_ovs_starts_service_if_required(self, mock_config, + _use_dvr): + _use_dvr = 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): + def test_configure_ovs_doesnt_restart_service(self, mock_config, _usedvr): + _use_dvr = 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): + def test_configure_ovs_ovs_ext_port(self, mock_config, _usedvr): + _use_dvr = False mock_config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get self.test_config.set('ext-port', 'eth0') From 8f1827f88c7a1fadd8e383f0c2501dfb64503500 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Mar 2015 10:27:32 +0000 Subject: [PATCH 22/27] Lint cleanup --- hooks/neutron_ovs_context.py | 10 ---------- unit_tests/test_neutron_ovs_utils.py | 21 +++++---------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index c5d4b1d1..e6b71a4c 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -6,24 +6,15 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.contrib.openstack.ip import resolve_address 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.network.ip import get_address_in_network from charmhelpers.contrib.openstack.context import ( OSContextGenerator, NeutronAPIContext, - DataPortContext, ) from charmhelpers.contrib.openstack.neutron import ( - parse_bridge_mappings, parse_vlan_range_mappings, ) -OVS_BRIDGE = 'br-int' class OVSPluginContext(context.NeutronContext): @@ -44,7 +35,6 @@ class OVSPluginContext(context.NeutronContext): neutron_api_settings = NeutronAPIContext()() return neutron_api_settings['neutron_security_groups'] - def ovs_ctxt(self): # In addition to generating config context, ensure the OVS service # is running and the OVS bridge exists. Also need to ensure diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index 7eb798f7..abe5a3a5 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -167,7 +167,7 @@ class TestNeutronOVSUtils(CharmTestCase): @patch('charmhelpers.contrib.openstack.context.config') def test_configure_ovs_starts_service_if_required(self, mock_config, _use_dvr): - _use_dvr = False + _use_dvr.return_value = False mock_config.side_effect = self.test_config.get self.config.return_value = 'ovs' self.service_running.return_value = False @@ -176,8 +176,8 @@ class TestNeutronOVSUtils(CharmTestCase): @patch.object(nutils, 'use_dvr') @patch('charmhelpers.contrib.openstack.context.config') - def test_configure_ovs_doesnt_restart_service(self, mock_config, _usedvr): - _use_dvr = False + 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 @@ -186,8 +186,8 @@ class TestNeutronOVSUtils(CharmTestCase): @patch.object(nutils, 'use_dvr') @patch('charmhelpers.contrib.openstack.context.config') - def test_configure_ovs_ovs_ext_port(self, mock_config, _usedvr): - _use_dvr = False + def test_configure_ovs_ovs_ext_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.test_config.set('ext-port', 'eth0') @@ -200,17 +200,6 @@ class TestNeutronOVSUtils(CharmTestCase): call('br-data') ]) self.add_bridge_port.assert_called_with('br-ex', 'eth0') -# @patch.object(context, 'ExternalPortContext') -# def test_configure_ovs_ovs_ext_port(self, _ext_port_ctxt): -# _ext_port_ctxt.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): From a429ae1a8b004543762b3bfe28499861f3475291 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Mar 2015 10:35:54 +0000 Subject: [PATCH 23/27] Fix juno templates --- charm-helpers-sync.yaml | 2 +- templates/juno/ml2_conf.ini | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml index c4fc1cb1..9b5e79e9 100644 --- a/charm-helpers-sync.yaml +++ b/charm-helpers-sync.yaml @@ -1,4 +1,4 @@ -branch: lp:~gnuoy/charm-helpers/neutron-shuffle +branch: lp:charm-helpers destination: hooks/charmhelpers include: - core diff --git a/templates/juno/ml2_conf.ini b/templates/juno/ml2_conf.ini index 1a0c7c93..7cf2706a 100644 --- a/templates/juno/ml2_conf.ini +++ b/templates/juno/ml2_conf.ini @@ -16,20 +16,23 @@ tunnel_id_ranges = 1:1000 vni_ranges = 1001:2000 [ml2_type_vlan] -network_vlan_ranges = physnet1:1000:2000 +network_vlan_ranges = {{ vlan_ranges }} [ml2_type_flat] -flat_networks = physnet1 +flat_networks = {{ network_providers }} [ovs] enable_tunneling = True local_ip = {{ local_ip }} -bridge_mappings = physnet1:br-data +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 -%} From 502156897b283bc03eb2a9783da01d92e5824e16 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 30 Mar 2015 15:10:41 +0000 Subject: [PATCH 24/27] Update with fixes from mp --- hooks/neutron_ovs_utils.py | 10 +++++----- templates/ext-port.conf | 11 +++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index fccf9309..1c892304 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -36,7 +36,7 @@ NEUTRON_FWAAS_CONF = "/etc/neutron/fwaas_driver.ini" 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-vpn-agent'] +DVR_PACKAGES = ['neutron-l3-agent'] PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf' TEMPLATES = 'templates/' @@ -57,15 +57,15 @@ BASE_RESOURCE_MAP = OrderedDict([ ]) DVR_RESOURCE_MAP = OrderedDict([ (NEUTRON_L3_AGENT_CONF, { - 'services': ['neutron-vpn-agent'], + 'services': ['neutron-l3-agent'], 'contexts': [neutron_ovs_context.L3AgentContext()], }), (NEUTRON_FWAAS_CONF, { - 'services': ['neutron-vpn-agent'], + 'services': ['neutron-l3-agent'], 'contexts': [neutron_ovs_context.L3AgentContext()], }), (EXT_PORT_CONF, { - 'services': ['neutron-vpn-agent'], + 'services': ['neutron-l3-agent'], 'contexts': [context.ExternalPortContext()], }), (NEUTRON_METADATA_AGENT_CONF, { @@ -111,7 +111,7 @@ def resource_map(): resource_map = deepcopy(BASE_RESOURCE_MAP) if use_dvr(): resource_map.update(DVR_RESOURCE_MAP) - dvr_services = ['neutron-metadata-agent', 'neutron-vpn-agent'] + dvr_services = ['neutron-metadata-agent', 'neutron-l3-agent'] resource_map[NEUTRON_CONF]['services'] += dvr_services return resource_map diff --git a/templates/ext-port.conf b/templates/ext-port.conf index 6080c30e..1d850240 100644 --- a/templates/ext-port.conf +++ b/templates/ext-port.conf @@ -5,5 +5,12 @@ start on runlevel [2345] task script - ip link set {{ ext_port }} up -end script \ No newline at end of file + 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 From fe1406ebeceb27af52bb7be2242a63452127876b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 31 Mar 2015 14:33:06 +0000 Subject: [PATCH 25/27] Get keystone creds from neutron-api not nova-cc --- .../charmhelpers/contrib/openstack/context.py | 9 ++++---- hooks/neutron_ovs_context.py | 22 +++++++++++++++++++ hooks/neutron_ovs_utils.py | 4 +--- metadata.yaml | 2 -- templates/juno/metadata_agent.ini | 9 ++++---- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 45e65790..dd51bfbb 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -320,14 +320,15 @@ def db_ssl(rdata, ctxt, ssl_dir): 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_user = service_user + self.rel_name = rel_name + self.interfaces = [self.rel_name] def __call__(self): - log('Generating template context for identity-service', level=DEBUG) + log('Generating template context for ' + self.rel_name, level=DEBUG) ctxt = {} if self.service and self.service_user: @@ -341,7 +342,7 @@ class IdentityServiceContext(OSContextGenerator): 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): rdata = relation_get(rid=rid, unit=unit) serv_host = rdata.get('service_host') diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index e6b71a4c..be1a7839 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -2,6 +2,9 @@ import os import uuid from charmhelpers.core.hookenv import ( config, + relation_get, + relation_ids, + related_units, unit_get, ) from charmhelpers.contrib.openstack.ip import resolve_address @@ -118,3 +121,22 @@ class DVRSharedSecretContext(OSContextGenerator): 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 + diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index 1c892304..233e5313 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -71,9 +71,7 @@ DVR_RESOURCE_MAP = OrderedDict([ (NEUTRON_METADATA_AGENT_CONF, { 'services': ['neutron-metadata-agent'], 'contexts': [neutron_ovs_context.DVRSharedSecretContext(), - context.NetworkServiceContext( - rel_name='neutron-network-service' - )], + neutron_ovs_context.APIIdentityServiceContext()], }), ]) TEMPLATES = 'templates/' diff --git a/metadata.yaml b/metadata.yaml index 790cda80..0e840258 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -20,8 +20,6 @@ provides: neutron-plugin: interface: neutron-plugin scope: container - neutron-network-service: - interface: quantum requires: amqp: interface: rabbitmq diff --git a/templates/juno/metadata_agent.ini b/templates/juno/metadata_agent.ini index 3062d697..c64d057c 100644 --- a/templates/juno/metadata_agent.ini +++ b/templates/juno/metadata_agent.ini @@ -4,12 +4,13 @@ ############################################################################### # Metadata service seems to cache neutron api url from keystone so trigger # restart if it changes: {{ quantum_url }} + [DEFAULT] -auth_url = {{ service_protocol }}://{{ keystone_host }}:{{ service_port }}/v2.0 +auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0 auth_region = {{ region }} -admin_tenant_name = {{ service_tenant }} -admin_user = {{ service_username }} -admin_password = {{ service_password }} +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 From 83d814c810a69d7a1ba8ac4f0ff3e7d4303ad7c7 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 1 Apr 2015 09:57:10 +0100 Subject: [PATCH 26/27] Fix up lint and unit tests. Unit tests needed fixing as _ensure_bridge has been removed from the OVSPluginContext in favour of doing it explicitly in configure_ovs() --- hooks/neutron_ovs_context.py | 2 +- unit_tests/test_neutron_ovs_context.py | 23 +++++++++-------------- unit_tests/test_neutron_ovs_utils.py | 6 +++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index be1a7839..4ca582f9 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -122,6 +122,7 @@ class DVRSharedSecretContext(OSContextGenerator): ctxt = {} return ctxt + class APIIdentityServiceContext(context.IdentityServiceContext): def __init__(self): @@ -139,4 +140,3 @@ class APIIdentityServiceContext(context.IdentityServiceContext): if ctxt['region']: return ctxt return ctxt - diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py index 93fab0ff..72fb5543 100644 --- a/unit_tests/test_neutron_ovs_context.py +++ b/unit_tests/test_neutron_ovs_context.py @@ -4,15 +4,11 @@ from test_utils import patch_open from mock import patch import neutron_ovs_context as context import charmhelpers + TO_PATCH = [ 'resolve_address', 'config', 'unit_get', - 'add_bridge', - 'add_bridge_port', - 'service_running', - 'service_start', - 'service_restart', 'get_host_ip', ] @@ -44,8 +40,10 @@ class OVSPluginContextTest(CharmTestCase): self.test_config.set('data-port', 'br-data:em1') config.side_effect = self.test_config.get mock_resolve_ports.side_effect = lambda ports: ports - self.assertEquals(context.DataPortContext()(), - {'br-data': 'em1'}) + self.assertEquals( + charmhelpers.contrib.openstack.context.DataPortContext()(), + {'br-data': 'em1'} + ) @patch('charmhelpers.contrib.openstack.context.config') @patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr') @@ -62,8 +60,10 @@ class OVSPluginContextTest(CharmTestCase): config.side_effect = self.test_config.get list_nics.return_value = machine_machs.keys() get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic] - self.assertEquals(context.DataPortContext()(), - {'br-d2': 'em1'}) + self.assertEquals( + charmhelpers.contrib.openstack.context.DataPortContext()(), + {'br-d2': 'em1'} + ) @patch.object(charmhelpers.contrib.openstack.context, 'relation_get') @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids') @@ -101,7 +101,6 @@ class OVSPluginContextTest(CharmTestCase): } _rget.side_effect = lambda *args, **kwargs: rdata self.get_host_ip.return_value = '127.0.0.15' - self.service_running.return_value = False napi_ctxt = context.OVSPluginContext() expect = { 'neutron_alchemy_flags': {}, @@ -125,7 +124,6 @@ class OVSPluginContextTest(CharmTestCase): 'vlan_ranges': 'physnet1:1000:2000', } self.assertEquals(expect, napi_ctxt()) - self.service_start.assertCalled() @patch.object(charmhelpers.contrib.openstack.context, 'relation_get') @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids') @@ -168,7 +166,6 @@ class OVSPluginContextTest(CharmTestCase): } _rget.side_effect = lambda *args, **kwargs: rdata self.get_host_ip.return_value = '127.0.0.15' - self.service_running.return_value = False napi_ctxt = context.OVSPluginContext() expect = { 'distributed_routing': False, @@ -192,7 +189,6 @@ class OVSPluginContextTest(CharmTestCase): 'vlan_ranges': 'physnet1:1000:2000', } self.assertEquals(expect, napi_ctxt()) - self.service_start.assertCalled() class L3AgentContextTest(CharmTestCase): @@ -272,7 +268,6 @@ class DVRSharedSecretContext(CharmTestCase): _NeutronAPIContext): _NeutronAPIContext.side_effect = fake_context({'enable_dvr': True}) _shared_secret.return_value = 'secret_thing' - #_use_dvr.return_value = True self.resolve_address.return_value = '10.0.0.10' self.assertEquals(context.DVRSharedSecretContext()(), {'shared_secret': 'secret_thing', diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index abe5a3a5..dd1c5328 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -22,8 +22,8 @@ TO_PATCH = [ 'os_release', 'neutron_plugin_attribute', 'full_restart', - 'service_running', 'service_restart', + 'service_running', 'ExternalPortContext', ] @@ -111,7 +111,7 @@ class TestNeutronOVSUtils(CharmTestCase): _use_dvr.return_value = True _map = nutils.resource_map() svcs = ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent', - 'neutron-vpn-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) @@ -187,7 +187,7 @@ class TestNeutronOVSUtils(CharmTestCase): @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 = False + _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') From c719eb2dd2681eb49c92d701a3059adaaeb94dff Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 1 Apr 2015 12:28:07 +0100 Subject: [PATCH 27/27] Fix template header --- templates/juno/ml2_conf.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/juno/ml2_conf.ini b/templates/juno/ml2_conf.ini index 7cf2706a..f798463a 100644 --- a/templates/juno/ml2_conf.ini +++ b/templates/juno/ml2_conf.ini @@ -1,4 +1,4 @@ -# icehouse +# juno ############################################################################### # [ WARNING ] # Configuration file maintained by Juju. Local changes may be overwritten.