diff --git a/Makefile b/Makefile index 53ede91..338cf59 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ virtualenv: netaddr jinja2 lint: virtualenv - .venv/bin/flake8 --exclude hooks/charmhelpers hooks unit_tests tests + .venv/bin/flake8 --exclude hooks/charmhelpers hooks unit_tests tests --ignore E402 @charm proof unit_test: virtualenv diff --git a/config.yaml b/config.yaml index 93f1164..c450253 100644 --- a/config.yaml +++ b/config.yaml @@ -7,6 +7,28 @@ options: default: 'null' type: string description: Public SSH key of PLUMgrid LCM which is running PG-Tools. + mgmt-interface: + type: string + default: 'juju-br0' + description: The interface connected to PLUMgrid Managment network. + os-data-network: + type: string + default: + description: | + The IP address and netmask of the OpenStack Data network (e.g., + 192.168.0.0/24) + . + This network will be used for tenant network traffic in overlay + networks. + fabric-interfaces: + default: 'MANAGEMENT' + type: string + description: | + Interfaces that will provide fabric connectivity on the director nodes. + Provided in form of json in a string. These interfaces have to be connected + to the os-data-network specified in the config. Default value is MANAGEMENT which + will configure the management interface as the fabric interface on each + director. network-device-mtu: type: string default: '1580' @@ -19,6 +41,16 @@ options: default: null type: string description: Provide the respective keys of the install sources. + plumgrid-build: + default: 'latest' + type: string + description: | + Provide the build version of PLUMgrid packages that needs to be installed + iovisor-build: + default: 'latest' + type: string + description: | + Provide the build version of iovisor package that needs to be installed plumgrid-license-key: default: null type: string diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 91ce1e2..c43d857 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -204,8 +204,8 @@ def neutron_plugins(): database=config('database'), ssl_dir=NEUTRON_CONF_DIR)], 'services': [], - 'packages': [['plumgrid-lxc'], - ['iovisor-dkms']], + 'packages': ['plumgrid-lxc', + 'iovisor-dkms'], 'server_packages': ['neutron-server', 'neutron-plugin-plumgrid'], 'server_services': ['neutron-server'] diff --git a/hooks/pg_dir_context.py b/hooks/pg_dir_context.py index 11a1900..94b6834 100644 --- a/hooks/pg_dir_context.py +++ b/hooks/pg_dir_context.py @@ -29,7 +29,7 @@ def _pg_dir_ips(): for rid in relation_ids('director'): for unit in related_units(rid): rdata = relation_get(rid=rid, unit=unit) - pg_dir_ips.append(rdata['private-address']) + pg_dir_ips.append(get_host_ip(rdata['private-address'])) return pg_dir_ips @@ -82,11 +82,12 @@ class PGDirContext(context.NeutronContext): pg_dir_ips_string = pg_dir_ips_string + ',' + str(ip) pg_ctxt['director_ips_string'] = pg_dir_ips_string pg_ctxt['virtual_ip'] = conf['plumgrid-virtual-ip'] - pg_ctxt['pg_hostname'] = "pg-director" - from pg_dir_utils import check_interface_type - interface_type = check_interface_type() - pg_ctxt['interface'] = interface_type - pg_ctxt['label'] = get_unit_hostname() + unit_hostname = get_unit_hostname() + pg_ctxt['pg_hostname'] = unit_hostname + from pg_dir_utils import get_mgmt_interface, get_fabric_interface + pg_ctxt['interface'] = get_mgmt_interface() + pg_ctxt['fabric_interface'] = get_fabric_interface() + pg_ctxt['label'] = unit_hostname pg_ctxt['fabric_mode'] = 'host' virtual_ip_array = re.split('\.', conf['plumgrid-virtual-ip']) pg_ctxt['virtual_router_id'] = virtual_ip_array[3] diff --git a/hooks/pg_dir_hooks.py b/hooks/pg_dir_hooks.py index a0c2fc4..6ba6cba 100755 --- a/hooks/pg_dir_hooks.py +++ b/hooks/pg_dir_hooks.py @@ -30,6 +30,7 @@ from pg_dir_utils import ( ensure_mtu, add_lcm_key, post_pg_license, + fabric_interface_changed ) hooks = Hooks() @@ -47,7 +48,7 @@ def install(): apt_install(pkg, options=['--force-yes'], fatal=True) load_iovisor() ensure_mtu() - add_lcm_key() + CONFIGS.write_all() @hooks.hook('director-relation-joined') @@ -65,17 +66,28 @@ def config_changed(): This hook is run when a config parameter is changed. It also runs on node reboot. ''' - if post_pg_license(): - log("PLUMgrid License Posted") - return 1 if add_lcm_key(): log("PLUMgrid LCM Key added") return 1 + charm_config = config() + if charm_config.changed('plumgrid-license-key'): + if post_pg_license(): + log("PLUMgrid License Posted") + return 1 + if charm_config.changed('fabric-interfaces'): + if not fabric_interface_changed(): + log("Fabric interface already set") + return 1 + if charm_config.changed('os-data-network'): + if charm_config['fabric-interfaces'] == 'MANAGEMENT': + log('Fabric running on managment network') + return 1 stop_pg() configure_sources(update=True) pkgs = determine_packages() for pkg in pkgs: apt_install(pkg, options=['--force-yes'], fatal=True) + remove_iovisor() load_iovisor() ensure_mtu() add_lcm_key() diff --git a/hooks/pg_dir_utils.py b/hooks/pg_dir_utils.py index cda7dea..c437a6f 100644 --- a/hooks/pg_dir_utils.py +++ b/hooks/pg_dir_utils.py @@ -7,6 +7,18 @@ from copy import deepcopy from charmhelpers.core.hookenv import ( log, config, + unit_get, +) +from charmhelpers.contrib.network.ip import ( + get_iface_from_addr, + get_bridges, + get_bridge_nics, + is_ip, + is_address_in_network, + get_iface_addr +) +from charmhelpers.fetch import ( + apt_cache ) from charmhelpers.contrib.openstack import templating from charmhelpers.core.host import set_nic_mtu @@ -19,11 +31,11 @@ from charmhelpers.core.host import ( service_start, service_stop, ) +from socket import gethostname as get_unit_hostname import pg_dir_context import subprocess import time import os -import re import json LXC_CONF = '/etc/libvirt/lxc.conf' @@ -74,7 +86,25 @@ def determine_packages(): Returns list of packages required by PLUMgrid director as specified in the neutron_plugins dictionary in charmhelpers. ''' - return neutron_plugin_attribute('plumgrid', 'packages', 'neutron') + pkgs = [] + tag = 'latest' + for pkg in neutron_plugin_attribute('plumgrid', 'packages', 'neutron'): + if 'plumgrid' in pkg: + tag = config('plumgrid-build') + elif pkg == 'iovisor-dkms': + tag = config('iovisor-build') + + if tag == 'latest': + pkgs.append(pkg) + else: + if tag in [i.ver_str for i in apt_cache()[pkg].version_list]: + pkgs.append('%s=%s' % (pkg, tag)) + else: + error_msg = \ + "Build version '%s' for package '%s' not available" \ + % (tag, pkg) + raise ValueError(error_msg) + return pkgs def register_configs(release=None): @@ -139,38 +169,94 @@ def remove_iovisor(): ''' _exec_cmd(cmd=['rmmod', 'iovisor'], error_msg='Error Loading IOVisor Kernel Module') + time.sleep(1) -def check_interface_type(): +def interface_exists(interface): ''' - Checks the interface. Support added for AWS deployments. There are 2 - possible interfaces "juju-br0" and "eth0". The default being juju-br0 + Checks if interface exists on node. ''' - log("Checking Interface Type") - default_interface = "juju-br0" - AWS_interface = "eth0" - shell_output = subprocess.check_output(['brctl', 'show', 'juju-br0']) - output = re.split(' |\n|\t', shell_output) - if output[10] == '': - return AWS_interface + try: + subprocess.check_call(['ip', 'link', 'show', interface], + stdout=open(os.devnull, 'w'), + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + return False + return True + + +def get_mgmt_interface(): + ''' + Returns the managment interface. + ''' + mgmt_interface = config('mgmt-interface') + if interface_exists(mgmt_interface): + return mgmt_interface else: - return default_interface + log('Provided managment interface %s does not exist' + % mgmt_interface) + return get_iface_from_addr(unit_get('private-address')) + + +def fabric_interface_changed(): + ''' + Returns true if interface for node changed. + ''' + fabric_interface = get_fabric_interface() + try: + with open(PG_IFCS_CONF, 'r') as ifcs: + for line in ifcs: + if 'fabric_core' in line: + if line.split()[0] == fabric_interface: + return False + except IOError: + return True + return True + + +def get_fabric_interface(): + ''' + Returns the fabric interface. + ''' + fabric_interfaces = config('fabric-interfaces') + if fabric_interfaces == 'MANAGEMENT': + return get_mgmt_interface() + else: + try: + all_fabric_interfaces = json.loads(fabric_interfaces) + except ValueError: + raise ValueError('Invalid json provided for fabric interfaces') + hostname = get_unit_hostname() + if hostname in all_fabric_interfaces: + node_fabric_interface = all_fabric_interfaces[hostname] + elif 'DEFAULT' in all_fabric_interfaces: + node_fabric_interface = all_fabric_interfaces['DEFAULT'] + else: + raise ValueError('No fabric interface provided for node') + if interface_exists(node_fabric_interface): + if is_address_in_network(config('os-data-network'), + get_iface_addr(node_fabric_interface)[0]): + return node_fabric_interface + else: + raise ValueError('Fabric interface not in fabric network') + else: + log('Provided fabric interface %s does not exist' + % node_fabric_interface) + raise ValueError('Provided fabric interface does not exist') + return node_fabric_interface def ensure_mtu(): ''' Ensures required MTU of the underlying networking of the node. ''' - log("Changing MTU of juju-br0 and all attached interfaces") interface_mtu = config('network-device-mtu') - interface_type = check_interface_type() - if interface_type == "juju-br0": - cmd = subprocess.check_output(["brctl", "show", interface_type]) - words = cmd.split() - for word in words: - if 'eth' in word: - set_nic_mtu(word, interface_mtu) - set_nic_mtu(interface_type, interface_mtu) + fabric_interface = get_fabric_interface() + if fabric_interface in get_bridges(): + attached_interfaces = get_bridge_nics(fabric_interface) + for interface in attached_interfaces: + set_nic_mtu(interface, interface_mtu) + set_nic_mtu(fabric_interface, interface_mtu) def _exec_cmd(cmd=None, error_msg='Command exited with ERRORs', fatal=False): @@ -229,6 +315,8 @@ def post_pg_license(): log('PLUMgrid License Key not specified') return 0 PG_VIP = config('plumgrid-virtual-ip') + if not is_ip(PG_VIP): + raise ValueError('Invalid IP Provided') LICENSE_POST_PATH = 'https://%s/0/tenant_manager/license_key' % PG_VIP LICENSE_GET_PATH = 'https://%s/0/tenant_manager/licenses' % PG_VIP PG_CURL = '%s/opt/pg/scripts/pg_curl.sh' % PG_LXC_PATH @@ -239,8 +327,7 @@ def post_pg_license(): 'plumgrid:plumgrid', LICENSE_POST_PATH, '-d', - json.dumps(license) - ] + json.dumps(license)] licence_get_cmd = [PG_CURL, '-u', 'plumgrid:plumgrid', LICENSE_GET_PATH] try: old_license = subprocess.check_output(licence_get_cmd) diff --git a/templates/kilo/ifcs.conf b/templates/kilo/ifcs.conf index 2ac1e59..9659932 100644 --- a/templates/kilo/ifcs.conf +++ b/templates/kilo/ifcs.conf @@ -1,2 +1,2 @@ -{{ interface }} = fabric_core host +{{ fabric_interface }} = fabric_core host diff --git a/templates/kilo/nginx.conf b/templates/kilo/nginx.conf index 179bb67..00f58f1 100644 --- a/templates/kilo/nginx.conf +++ b/templates/kilo/nginx.conf @@ -12,6 +12,10 @@ upstream pgCli { server {{ virtual_ip }}:3000; } +upstream pgMW { + server 127.0.0.1:4000; +} + map $http_upgrade $connection_upgrade { default upgrade; '' close; @@ -58,6 +62,19 @@ server { proxy_set_header Host $host; } + location /mwv0 { + proxy_pass http://pgMW; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + location /cloudApex/ { + index index.html; + } + location /vtap/ { alias /opt/pg/vtap; } diff --git a/unit_tests/test_pg_dir_context.py b/unit_tests/test_pg_dir_context.py index 3a716eb..33d081b 100644 --- a/unit_tests/test_pg_dir_context.py +++ b/unit_tests/test_pg_dir_context.py @@ -45,12 +45,13 @@ class PGDirContextTest(CharmTestCase): 'neutron_plugin_attribute') @patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip') @patch.object(context, '_pg_dir_ips') - @patch.object(utils, 'check_interface_type') - def test_neutroncc_context_api_rel(self, _int_type, _pg_dir_ips, - _unit_priv_ip, _npa, _ens_pkgs, - _save_ff, _https, _is_clus, - _unit_get, _config, _runits, _rids, - _rget): + @patch.object(utils, 'get_mgmt_interface') + @patch.object(utils, 'get_fabric_interface') + def test_neutroncc_context_api_rel(self, _fabric_int, _mgmt_int, + _pg_dir_ips, _unit_priv_ip, _npa, + _ens_pkgs, _save_ff, _https, + _is_clus, _unit_get, _config, + _runits, _rids, _rget): def mock_npa(plugin, section, manager): if section == "driver": return "neutron.randomdriver" @@ -73,7 +74,8 @@ class PGDirContextTest(CharmTestCase): self.get_unit_hostname.return_value = 'node0' self.get_host_ip.return_value = '192.168.100.201' _pg_dir_ips.return_value = ['192.168.100.202', '192.168.100.203'] - _int_type.return_value = 'juju-br0' + _mgmt_int.return_value = 'juju-br0' + _fabric_int.return_value = 'juju-br0' napi_ctxt = context.PGDirContext() expect = { 'config': 'neutron.randomconfig', @@ -84,8 +86,9 @@ class PGDirContextTest(CharmTestCase): 'neutron_security_groups': None, 'neutron_url': 'https://None:9696', 'virtual_ip': '192.168.100.250', - 'pg_hostname': 'pg-director', + 'pg_hostname': 'node0', 'interface': 'juju-br0', + 'fabric_interface': 'juju-br0', 'label': 'node0', 'fabric_mode': 'host', 'virtual_router_id': '250', diff --git a/unit_tests/test_pg_dir_hooks.py b/unit_tests/test_pg_dir_hooks.py index 2fa95a4..dd0b850 100644 --- a/unit_tests/test_pg_dir_hooks.py +++ b/unit_tests/test_pg_dir_hooks.py @@ -1,5 +1,7 @@ from mock import MagicMock, patch, call + from test_utils import CharmTestCase + with patch('charmhelpers.core.hookenv.config') as config: config.return_value = 'neutron' import pg_dir_utils as utils @@ -58,25 +60,10 @@ class PGDirHooksTests(CharmTestCase): ]) self.load_iovisor.assert_called_with() self.ensure_mtu.assert_called_with() - self.add_lcm_key.assert_called_with() def test_config_changed_hook(self): - _pkgs = ['plumgrid-lxc', 'iovisor-dkms'] - self.add_lcm_key.return_value = 0 - self.post_pg_license.return_value = 0 - self.determine_packages.return_value = [_pkgs] + self.add_lcm_key.return_value = 1 self._call_hook('config-changed') - self.stop_pg.assert_called_with() - self.configure_sources.assert_called_with(update=True) - self.apt_install.assert_has_calls([ - call(_pkgs, fatal=True, - options=['--force-yes']), - ]) - self.load_iovisor.assert_called_with() - self.ensure_mtu.assert_called_with() - - self.CONFIGS.write_all.assert_called_with() - self.restart_pg.assert_called_with() def test_start(self): self._call_hook('start')