diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index 67b4dccc..7f3b66b1 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -23,7 +23,7 @@ import socket from functools import partial from charmhelpers.core.hookenv import unit_get -from charmhelpers.fetch import apt_install +from charmhelpers.fetch import apt_install, apt_update from charmhelpers.core.hookenv import ( log, WARNING, @@ -32,13 +32,15 @@ from charmhelpers.core.hookenv import ( try: import netifaces except ImportError: - apt_install('python-netifaces') + apt_update(fatal=True) + apt_install('python-netifaces', fatal=True) import netifaces try: import netaddr except ImportError: - apt_install('python-netaddr') + apt_update(fatal=True) + apt_install('python-netaddr', fatal=True) import netaddr diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/hooks/charmhelpers/contrib/openstack/amulet/utils.py index b1397419..2b3087ea 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/utils.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/utils.py @@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils): self.log.debug('SSL is enabled @{}:{} ' '({})'.format(host, port, unit_name)) return True - elif not port and not conf_ssl: + elif not conf_ssl: self.log.debug('SSL not enabled @{}:{} ' '({})'.format(host, port, unit_name)) return False diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 1248d49f..076790d6 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with charm-helpers. If not, see . +import glob import json import os import re @@ -951,6 +952,19 @@ class NeutronContext(OSContextGenerator): 'config': config} return ovs_ctxt + def midonet_ctxt(self): + driver = neutron_plugin_attribute(self.plugin, 'driver', + self.network_manager) + midonet_config = neutron_plugin_attribute(self.plugin, 'config', + self.network_manager) + mido_ctxt = {'core_plugin': driver, + 'neutron_plugin': 'midonet', + 'neutron_security_groups': self.neutron_security_groups, + 'local_ip': unit_private_ip(), + 'config': midonet_config} + + return mido_ctxt + def __call__(self): if self.network_manager not in ['quantum', 'neutron']: return {} @@ -972,6 +986,8 @@ class NeutronContext(OSContextGenerator): ctxt.update(self.nuage_ctxt()) elif self.plugin == 'plumgrid': ctxt.update(self.pg_ctxt()) + elif self.plugin == 'midonet': + ctxt.update(self.midonet_ctxt()) alchemy_flags = config('neutron-alchemy-flags') if alchemy_flags: @@ -1363,7 +1379,7 @@ class DataPortContext(NeutronPortContext): normalized.update({port: port for port in resolved if port in ports}) if resolved: - return {bridge: normalized[port] for port, bridge in + return {normalized[port]: bridge for port, bridge in six.iteritems(portmap) if port in normalized.keys()} return None @@ -1374,12 +1390,22 @@ class PhyNICMTUContext(DataPortContext): def __call__(self): ctxt = {} mappings = super(PhyNICMTUContext, self).__call__() - if mappings and mappings.values(): - ports = mappings.values() + if mappings and mappings.keys(): + ports = sorted(mappings.keys()) napi_settings = NeutronAPIContext()() mtu = napi_settings.get('network_device_mtu') + all_ports = set() + # If any of ports is a vlan device, its underlying device must have + # mtu applied first. + for port in ports: + for lport in glob.glob("/sys/class/net/%s/lower_*" % port): + lport = os.path.basename(lport) + all_ports.add(lport.split('_')[1]) + + all_ports = list(all_ports) + all_ports.extend(ports) if mtu: - ctxt["devs"] = '\\n'.join(ports) + ctxt["devs"] = '\\n'.join(all_ports) ctxt['mtu'] = mtu return ctxt diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 55b2037f..c54d63c7 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -209,6 +209,20 @@ def neutron_plugins(): 'server_packages': ['neutron-server', 'neutron-plugin-plumgrid'], 'server_services': ['neutron-server'] + }, + 'midonet': { + 'config': '/etc/neutron/plugins/midonet/midonet.ini', + 'driver': 'midonet.neutron.plugin.MidonetPluginV2', + 'contexts': [ + context.SharedDBContext(user=config('neutron-database-user'), + database=config('neutron-database'), + relation_prefix='neutron', + ssl_dir=NEUTRON_CONF_DIR)], + 'services': [], + 'packages': [[headers_package()] + determine_dkms_package()], + 'server_packages': ['neutron-server', + 'python-neutron-plugin-midonet'], + 'server_services': ['neutron-server'] } } if release >= 'icehouse': @@ -310,10 +324,10 @@ def parse_bridge_mappings(mappings): def parse_data_port_mappings(mappings, default_bridge='br-data'): """Parse data port mappings. - Mappings must be a space-delimited list of port:bridge mappings. + Mappings must be a space-delimited list of bridge:port. - Returns dict of the form {port:bridge} where port may be an mac address or - interface name. + Returns dict of the form {port:bridge} where ports may be mac addresses or + interface names. """ # NOTE(dosaboy): we use rvalue for key to allow multiple values to be diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index ab53a780..c2bee134 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -623,6 +623,38 @@ def unit_private_ip(): return unit_get('private-address') +@cached +def storage_get(attribute="", storage_id=""): + """Get storage attributes""" + _args = ['storage-get', '--format=json'] + if storage_id: + _args.extend(('-s', storage_id)) + if attribute: + _args.append(attribute) + try: + return json.loads(subprocess.check_output(_args).decode('UTF-8')) + except ValueError: + return None + + +@cached +def storage_list(storage_name=""): + """List the storage IDs for the unit""" + _args = ['storage-list', '--format=json'] + if storage_name: + _args.append(storage_name) + try: + return json.loads(subprocess.check_output(_args).decode('UTF-8')) + except ValueError: + return None + except OSError as e: + import errno + if e.errno == errno.ENOENT: + # storage-list does not exist + return [] + raise + + class UnregisteredHookError(Exception): """Raised when an undefined hook is called""" pass diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf index 33c73198..f1725f32 100644 --- a/templates/juno/nova.conf +++ b/templates/juno/nova.conf @@ -63,7 +63,7 @@ vnc_enabled = False novnc_enabled = False {% endif -%} -{% if neutron_plugin and neutron_plugin == 'ovs' -%} +{% if neutron_plugin and neutron_plugin in ('ovs', 'midonet') -%} libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver {% if neutron_security_groups -%} security_group_api = neutron diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf index c3cbea7f..f4f18483 100644 --- a/templates/kilo/nova.conf +++ b/templates/kilo/nova.conf @@ -45,7 +45,7 @@ vnc_enabled = False novnc_enabled = False {% endif -%} -{% if neutron_plugin and neutron_plugin == 'ovs' -%} +{% if neutron_plugin and neutron_plugin in ('ovs', 'midonet') -%} libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver {% if neutron_security_groups -%} security_group_api = neutron diff --git a/tests/charmhelpers/contrib/amulet/utils.py b/tests/charmhelpers/contrib/amulet/utils.py index 55c86347..2591a9b1 100644 --- a/tests/charmhelpers/contrib/amulet/utils.py +++ b/tests/charmhelpers/contrib/amulet/utils.py @@ -326,7 +326,7 @@ class AmuletUtils(object): def service_restarted_since(self, sentry_unit, mtime, service, pgrep_full=None, sleep_time=20, - retry_count=2, retry_sleep_time=30): + retry_count=30, retry_sleep_time=10): """Check if service was been started after a given time. Args: @@ -334,8 +334,9 @@ class AmuletUtils(object): mtime (float): The epoch time to check against service (string): service name to look for in process table pgrep_full: [Deprecated] Use full command line search mode with pgrep - sleep_time (int): Seconds to sleep before looking for process - retry_count (int): If service is not found, how many times to retry + sleep_time (int): Initial sleep time (s) before looking for file + retry_sleep_time (int): Time (s) to sleep between retries + retry_count (int): If file is not found, how many times to retry Returns: bool: True if service found and its start time it newer than mtime, @@ -359,11 +360,12 @@ class AmuletUtils(object): pgrep_full) self.log.debug('Attempt {} to get {} proc start time on {} ' 'OK'.format(tries, service, unit_name)) - except IOError: + except IOError as e: # NOTE(beisner) - race avoidance, proc may not exist yet. # https://bugs.launchpad.net/charm-helpers/+bug/1474030 self.log.debug('Attempt {} to get {} proc start time on {} ' - 'failed'.format(tries, service, unit_name)) + 'failed\n{}'.format(tries, service, + unit_name, e)) time.sleep(retry_sleep_time) tries += 1 @@ -383,35 +385,62 @@ class AmuletUtils(object): return False def config_updated_since(self, sentry_unit, filename, mtime, - sleep_time=20): + sleep_time=20, retry_count=30, + retry_sleep_time=10): """Check if file was modified after a given time. Args: sentry_unit (sentry): The sentry unit to check the file mtime on filename (string): The file to check mtime of mtime (float): The epoch time to check against - sleep_time (int): Seconds to sleep before looking for process + sleep_time (int): Initial sleep time (s) before looking for file + retry_sleep_time (int): Time (s) to sleep between retries + retry_count (int): If file is not found, how many times to retry Returns: bool: True if file was modified more recently than mtime, False if - file was modified before mtime, + file was modified before mtime, or if file not found. """ - self.log.debug('Checking %s updated since %s' % (filename, mtime)) + unit_name = sentry_unit.info['unit_name'] + self.log.debug('Checking that %s updated since %s on ' + '%s' % (filename, mtime, unit_name)) time.sleep(sleep_time) - file_mtime = self._get_file_mtime(sentry_unit, filename) + file_mtime = None + tries = 0 + while tries <= retry_count and not file_mtime: + try: + file_mtime = self._get_file_mtime(sentry_unit, filename) + self.log.debug('Attempt {} to get {} file mtime on {} ' + 'OK'.format(tries, filename, unit_name)) + except IOError as e: + # NOTE(beisner) - race avoidance, file may not exist yet. + # https://bugs.launchpad.net/charm-helpers/+bug/1474030 + self.log.debug('Attempt {} to get {} file mtime on {} ' + 'failed\n{}'.format(tries, filename, + unit_name, e)) + time.sleep(retry_sleep_time) + tries += 1 + + if not file_mtime: + self.log.warn('Could not determine file mtime, assuming ' + 'file does not exist') + return False + if file_mtime >= mtime: self.log.debug('File mtime is newer than provided mtime ' - '(%s >= %s)' % (file_mtime, mtime)) + '(%s >= %s) on %s (OK)' % (file_mtime, + mtime, unit_name)) return True else: - self.log.warn('File mtime %s is older than provided mtime %s' - % (file_mtime, mtime)) + self.log.warn('File mtime is older than provided mtime' + '(%s < on %s) on %s' % (file_mtime, + mtime, unit_name)) return False def validate_service_config_changed(self, sentry_unit, mtime, service, filename, pgrep_full=None, - sleep_time=20, retry_count=2, - retry_sleep_time=30): + sleep_time=20, retry_count=30, + retry_sleep_time=10): """Check service and file were updated after mtime Args: @@ -456,7 +485,9 @@ class AmuletUtils(object): sentry_unit, filename, mtime, - sleep_time=0) + sleep_time=sleep_time, + retry_count=retry_count, + retry_sleep_time=retry_sleep_time) return service_restart and config_update diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index b1397419..2b3087ea 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils): self.log.debug('SSL is enabled @{}:{} ' '({})'.format(host, port, unit_name)) return True - elif not port and not conf_ssl: + elif not conf_ssl: self.log.debug('SSL not enabled @{}:{} ' '({})'.format(host, port, unit_name)) return False