diff --git a/README.md b/README.md index 0b9ca8f..ee940b4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This is an early access version of the PLUMgrid Gateway charm and it is not mean Example Config plumgrid-gateway: - external-interface: eth1 + external-interfaces: '{"node01":"eth5,eth2","node02":"eth4,eth8"}' install_sources: 'ppa:plumgrid-team/stable' install_keys: 'null' plumgrid-edge: @@ -54,7 +54,7 @@ Example Config neutron-plugin: "plumgrid" plumgrid-virtual-ip: "192.168.100.250" -The "external-interface" config parameter should be the interface that will provide external connectivity. +The "external-interfaces" config parameter should be the interfaces that will provide external connectivity on each of the gateway nodes. Should be provided as a json in a string with hostname and interface names. Provide the source repo path for PLUMgrid Debs in 'install_sources' and the corresponding keys in 'install_keys'. The virtual IP passed on in the neutron-api charm has to be same as the one passed in the plumgrid-director charm. diff --git a/config.yaml b/config.yaml index 1d3319c..8738892 100644 --- a/config.yaml +++ b/config.yaml @@ -1,8 +1,10 @@ options: - external-interface: - default: eth1 + external-interfaces: + default: '{"hostname":"eth1,eth2"}' type: string - description: The interface that will provide external connectivity + description: | + One or multiple interfaces that will provide external connectivity on + each of the gateway nodes. Provided in form of json in a string. lcm-ssh-key: default: 'null' type: string diff --git a/hooks/pg_gw_context.py b/hooks/pg_gw_context.py index 6cb6b46..1250ef9 100644 --- a/hooks/pg_gw_context.py +++ b/hooks/pg_gw_context.py @@ -7,7 +7,6 @@ from charmhelpers.core.hookenv import ( relation_ids, related_units, relation_get, - config, ) from charmhelpers.contrib.openstack import context @@ -60,7 +59,6 @@ class PGGwContext(context.NeutronContext): if not pg_ctxt: return {} - conf = config() pg_dir_ips = '' pg_dir_settings = _pg_dir_settings() single_ip = True @@ -73,12 +71,10 @@ class PGGwContext(context.NeutronContext): pg_ctxt['local_ip'] = pg_dir_ips unit_hostname = get_unit_hostname() pg_ctxt['pg_hostname'] = unit_hostname - from pg_gw_utils import check_interface_type - interface_type = check_interface_type() - pg_ctxt['interface'] = interface_type + from pg_gw_utils import check_interface_type, get_gw_interfaces + pg_ctxt['interface'] = check_interface_type() pg_ctxt['label'] = unit_hostname pg_ctxt['fabric_mode'] = 'host' - - pg_ctxt['ext_interface'] = conf['external-interface'] + pg_ctxt['ext_interfaces'] = get_gw_interfaces() return pg_ctxt diff --git a/hooks/pg_gw_utils.py b/hooks/pg_gw_utils.py index 930cc11..7a23398 100644 --- a/hooks/pg_gw_utils.py +++ b/hooks/pg_gw_utils.py @@ -20,11 +20,13 @@ from collections import OrderedDict from charmhelpers.contrib.openstack.utils import ( os_release, ) +from socket import gethostname as get_unit_hostname import pg_gw_context import subprocess import time import os import re +import json LXC_CONF = "/etc/libvirt/lxc.conf" TEMPLATES = 'templates/' @@ -35,6 +37,7 @@ PG_HN_CONF = '%s/conf/etc/hostname' % PG_LXC_DATA_PATH PG_HS_CONF = '%s/conf/etc/hosts' % PG_LXC_DATA_PATH PG_IFCS_CONF = '%s/conf/pg/ifcs.conf' % PG_LXC_DATA_PATH AUTH_KEY_PATH = '%s/root/.ssh/authorized_keys' % PG_LXC_DATA_PATH +IFC_LIST_GW = '/var/run/plumgrid/lxc/ifc_list_gateway' SUDOERS_CONF = '/etc/sudoers.d/ifc_ctl_sudoers' @@ -103,6 +106,7 @@ def ensure_files(): write_file(SUDOERS_CONF, "\nnova ALL=(root) NOPASSWD: /opt/pg/bin/ifc_ctl_pp *\n", owner='root', group='root', perms=0o644) + _exec_cmd(cmd=['rm', '-f', IFC_LIST_GW]) def restart_pg(): @@ -155,6 +159,23 @@ def check_interface_type(): return default_interface +def get_gw_interfaces(): + ''' + Gateway node can have multiple interfaces. This function parses json + provided in config to get all gateway interfaces for this node. + ''' + node_interfaces = ['eth1'] + try: + all_interfaces = json.loads(config('external-interfaces')) + except ValueError: + log("Invalid JSON") + return node_interfaces + hostname = get_unit_hostname() + if hostname in all_interfaces: + node_interfaces = all_interfaces[hostname].split(',') + return node_interfaces + + def ensure_mtu(): ''' Ensures required MTU of the underlying networking of the node. diff --git a/templates/kilo/ifcs.conf b/templates/kilo/ifcs.conf index 2c5794e..40166fe 100644 --- a/templates/kilo/ifcs.conf +++ b/templates/kilo/ifcs.conf @@ -1,6 +1,7 @@ {{ interface }} = fabric_core host -{% if ext_interface -%} -{{ ext_interface }} = access_phys - +{% if ext_interfaces -%} +{% for ip in ext_interfaces -%} +{{ ip }} = access_phys +{% endfor -%} {% endif -%} diff --git a/unit_tests/test_pg_gw_context.py b/unit_tests/test_pg_gw_context.py index 0ca70df..d8f2eef 100644 --- a/unit_tests/test_pg_gw_context.py +++ b/unit_tests/test_pg_gw_context.py @@ -5,7 +5,6 @@ import pg_gw_utils as utils import charmhelpers TO_PATCH = [ - 'config', 'get_unit_hostname', ] @@ -22,8 +21,6 @@ class PGGwContextTest(CharmTestCase): def setUp(self): super(PGGwContextTest, self).setUp(context, TO_PATCH) - self.config.side_effect = self.test_config.get - self.test_config.set('external-interface', 'eth1') def tearDown(self): super(PGGwContextTest, self).tearDown() @@ -41,7 +38,9 @@ class PGGwContextTest(CharmTestCase): @patch.object(charmhelpers.contrib.openstack.context, 'neutron_plugin_attribute') @patch.object(utils, 'check_interface_type') - def test_neutroncc_context_api_rel(self, _int_type, _npa, _pg_dir_settings, + @patch.object(utils, 'get_gw_interfaces') + def test_neutroncc_context_api_rel(self, _gw_int, _int_type, + _npa, _pg_dir_settings, _save_flag_file, _config_flag, _unit_get, _unit_priv_ip, _config, _is_clus, _https, _ens_pkgs): @@ -50,16 +49,8 @@ class PGGwContextTest(CharmTestCase): return "neutron.randomdriver" if section == "config": return "neutron.randomconfig" - config = {'external-interface': "eth1"} - - def mock_config(key=None): - if key: - return config.get(key) - - return config self.maxDiff = None - self.config.side_effect = mock_config _npa.side_effect = mock_npa _unit_get.return_value = '192.168.100.201' _unit_priv_ip.return_value = '192.168.100.201' @@ -68,9 +59,10 @@ class PGGwContextTest(CharmTestCase): _config_flag.return_value = False _pg_dir_settings.return_value = {'pg_dir_ip': '192.168.100.201'} _int_type.return_value = 'juju-br0' + _gw_int.return_value = ['eth1'] napi_ctxt = context.PGGwContext() expect = { - 'ext_interface': "eth1", + 'ext_interfaces': ['eth1'], 'config': 'neutron.randomconfig', 'core_plugin': 'neutron.randomdriver', 'local_ip': 'pg_dir_ip',