diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 8a982ffa..6331a92e 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -23,7 +23,6 @@ from charmhelpers.core.hookenv import ( unit_get, unit_private_ip, ERROR, - WARNING, ) from charmhelpers.contrib.hahelpers.cluster import ( @@ -182,10 +181,12 @@ class AMQPContext(OSContextGenerator): # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly - ctxt['rabbitmq_hosts'] = [] - for unit in related_units(rid): - ctxt['rabbitmq_hosts'].append(relation_get('private-address', - rid=rid, unit=unit)) + if 'clustered' not in ctxt and len(related_units(rid)) > 1: + rabbitmq_hosts = [] + for unit in related_units(rid): + rabbitmq_hosts.append(relation_get('private-address', + rid=rid, unit=unit)) + ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts) if not context_complete(ctxt): return {} else: @@ -286,6 +287,7 @@ class ImageServiceContext(OSContextGenerator): class ApacheSSLContext(OSContextGenerator): + """ Generates a context for an apache vhost configuration that configures HTTPS reverse proxying for one or many endpoints. Generated context @@ -433,28 +435,61 @@ class NeutronContext(object): class OSConfigFlagContext(OSContextGenerator): - ''' - Responsible adding user-defined config-flags in charm config to a - to a template context. - ''' + + """ + Responsible for adding user-defined config-flags in charm config to a + template context. + + NOTE: the value of config-flags may be a comma-separated list of + key=value pairs and some Openstack config files support + comma-separated lists as values. + """ + def __call__(self): config_flags = config('config-flags') - if not config_flags or config_flags in ['None', '']: + if not config_flags: return {} - config_flags = config_flags.split(',') + + if config_flags.find('==') >= 0: + log("config_flags is not in expected format (key=value)", + level=ERROR) + raise OSContextError + + # strip the following from each value. + post_strippers = ' ,' + # we strip any leading/trailing '=' or ' ' from the string then + # split on '='. + split = config_flags.strip(' =').split('=') + limit = len(split) flags = {} - for flag in config_flags: - if '=' not in flag: - log('Improperly formatted config-flag, expected k=v ' - 'got %s' % flag, level=WARNING) - continue - k, v = flag.split('=') - flags[k.strip()] = v - ctxt = {'user_config_flags': flags} - return ctxt + for i in xrange(0, limit - 1): + current = split[i] + next = split[i + 1] + vindex = next.rfind(',') + if (i == limit - 2) or (vindex < 0): + value = next + else: + value = next[:vindex] + + if i == 0: + key = current + else: + # if this not the first entry, expect an embedded key. + index = current.rfind(',') + if index < 0: + log("invalid config value(s) at index %s" % (i), + level=ERROR) + raise OSContextError + key = current[index + 1:] + + # Add to collection. + flags[key.strip(post_strippers)] = value.rstrip(post_strippers) + + return {'user_config_flags': flags} class SubordinateConfigContext(OSContextGenerator): + """ Responsible for inspecting relations to subordinates that may be exporting required config via a json blob. @@ -495,6 +530,7 @@ class SubordinateConfigContext(OSContextGenerator): } """ + def __init__(self, service, config_file, interface): """ :param service : Service name key to query in any subordinate diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index d66afd74..56d04245 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -41,6 +41,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('quantal', 'folsom'), ('raring', 'grizzly'), ('saucy', 'havana'), + ('trusty', 'icehouse') ]) @@ -201,7 +202,7 @@ def os_release(package, base='essex'): def import_key(keyid): - cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \ + cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 " \ "--recv-keys %s" % keyid try: subprocess.check_call(cmd.split(' ')) @@ -260,6 +261,9 @@ def configure_installation_source(rel): 'havana': 'precise-updates/havana', 'havana/updates': 'precise-updates/havana', 'havana/proposed': 'precise-proposed/havana', + 'icehouse': 'precise-updates/icehouse', + 'icehouse/updates': 'precise-updates/icehouse', + 'icehouse/proposed': 'precise-proposed/icehouse', } try: @@ -411,7 +415,7 @@ def get_host_ip(hostname): return ns_query(hostname) -def get_hostname(address): +def get_hostname(address, fqdn=True): """ Resolves hostname for given IP, or returns the input if it is already a hostname. @@ -430,7 +434,11 @@ def get_hostname(address): if not result: return None - # strip trailing . - if result.endswith('.'): - return result[:-1] - return result + if fqdn: + # strip trailing . + if result.endswith('.'): + return result[:-1] + else: + return result + else: + return result.split('.')[0] diff --git a/hooks/charmhelpers/contrib/storage/linux/utils.py b/hooks/charmhelpers/contrib/storage/linux/utils.py index 5b9b6d47..c40218f0 100644 --- a/hooks/charmhelpers/contrib/storage/linux/utils.py +++ b/hooks/charmhelpers/contrib/storage/linux/utils.py @@ -22,4 +22,4 @@ def zap_disk(block_device): :param block_device: str: Full path of block device to clean. ''' - check_call(['sgdisk', '--zap-all', block_device]) + check_call(['sgdisk', '--zap-all', '--mbrtogpt', block_device]) diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index bb196dfa..505c202d 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -8,6 +8,7 @@ import os import json import yaml import subprocess +import sys import UserDict from subprocess import CalledProcessError @@ -149,6 +150,11 @@ def service_name(): return local_unit().split('/')[0] +def hook_name(): + """The name of the currently executing hook""" + return os.path.basename(sys.argv[0]) + + @cached def config(scope=None): """Juju charm configuration""" diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index 4a6a4a8c..c8c81b28 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -245,3 +245,47 @@ def pwgen(length=None): random_chars = [ random.choice(alphanumeric_chars) for _ in range(length)] return(''.join(random_chars)) + + +def list_nics(nic_type): + '''Return a list of nics of given type(s)''' + if isinstance(nic_type, basestring): + int_types = [nic_type] + else: + int_types = nic_type + interfaces = [] + for int_type in int_types: + cmd = ['ip', 'addr', 'show', 'label', int_type + '*'] + ip_output = subprocess.check_output(cmd).split('\n') + ip_output = (line for line in ip_output if line) + for line in ip_output: + if line.split()[1].startswith(int_type): + interfaces.append(line.split()[1].replace(":", "")) + return interfaces + + +def set_nic_mtu(nic, mtu): + '''Set MTU on a network interface''' + cmd = ['ip', 'link', 'set', nic, 'mtu', mtu] + subprocess.check_call(cmd) + + +def get_nic_mtu(nic): + cmd = ['ip', 'addr', 'show', nic] + ip_output = subprocess.check_output(cmd).split('\n') + mtu = "" + for line in ip_output: + words = line.split() + if 'mtu' in words: + mtu = words[words.index("mtu") + 1] + return mtu + + +def get_nic_hwaddr(nic): + cmd = ['ip', '-o', '-0', 'addr', 'show', nic] + ip_output = subprocess.check_output(cmd) + hwaddr = "" + words = ip_output.split() + if 'link/ether' in words: + hwaddr = words[words.index('link/ether') + 1] + return hwaddr diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index fa0172a9..1f4f6315 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -13,6 +13,7 @@ from charmhelpers.core.hookenv import ( log, ) import apt_pkg +import os CLOUD_ARCHIVE = """# Ubuntu Cloud Archive deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main @@ -43,8 +44,16 @@ CLOUD_ARCHIVE_POCKETS = { 'precise-havana/updates': 'precise-updates/havana', 'precise-updates/havana': 'precise-updates/havana', 'havana/proposed': 'precise-proposed/havana', - 'precies-havana/proposed': 'precise-proposed/havana', + 'precise-havana/proposed': 'precise-proposed/havana', 'precise-proposed/havana': 'precise-proposed/havana', + # Icehouse + 'icehouse': 'precise-updates/icehouse', + 'precise-icehouse': 'precise-updates/icehouse', + 'precise-icehouse/updates': 'precise-updates/icehouse', + 'precise-updates/icehouse': 'precise-updates/icehouse', + 'icehouse/proposed': 'precise-proposed/icehouse', + 'precise-icehouse/proposed': 'precise-proposed/icehouse', + 'precise-proposed/icehouse': 'precise-proposed/icehouse', } @@ -66,8 +75,10 @@ def filter_installed_packages(packages): def apt_install(packages, options=None, fatal=False): """Install one or more packages""" - options = options or [] - cmd = ['apt-get', '-y'] + if options is None: + options = ['--option=Dpkg::Options::=--force-confold'] + + cmd = ['apt-get', '--assume-yes'] cmd.extend(options) cmd.append('install') if isinstance(packages, basestring): @@ -76,10 +87,14 @@ def apt_install(packages, options=None, fatal=False): cmd.extend(packages) log("Installing {} with options: {}".format(packages, options)) + env = os.environ.copy() + if 'DEBIAN_FRONTEND' not in env: + env['DEBIAN_FRONTEND'] = 'noninteractive' + if fatal: - subprocess.check_call(cmd) + subprocess.check_call(cmd, env=env) else: - subprocess.call(cmd) + subprocess.call(cmd, env=env) def apt_update(fatal=False): @@ -93,7 +108,7 @@ def apt_update(fatal=False): def apt_purge(packages, fatal=False): """Purge one or more packages""" - cmd = ['apt-get', '-y', 'purge'] + cmd = ['apt-get', '--assume-yes', 'purge'] if isinstance(packages, basestring): cmd.append(packages) else: @@ -123,14 +138,16 @@ def add_source(source, key=None): if (source.startswith('ppa:') or source.startswith('http:') or source.startswith('deb ') or - source.startswith('cloud-archive:')): + source.startswith('cloud-archive:')): subprocess.check_call(['add-apt-repository', '--yes', source]) elif source.startswith('cloud:'): apt_install(filter_installed_packages(['ubuntu-cloud-keyring']), fatal=True) pocket = source.split(':')[-1] if pocket not in CLOUD_ARCHIVE_POCKETS: - raise SourceConfigError('Unsupported cloud: source option %s' % pocket) + raise SourceConfigError( + 'Unsupported cloud: source option %s' % + pocket) actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket] with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: apt.write(CLOUD_ARCHIVE.format(actual_pocket)) @@ -220,7 +237,9 @@ def install_from_config(config_var_name): class BaseFetchHandler(object): + """Base class for FetchHandler implementations in fetch plugins""" + def can_handle(self, source): """Returns True if the source can be handled. Otherwise returns a string explaining why it cannot""" @@ -248,10 +267,13 @@ def plugins(fetch_handlers=None): for handler_name in fetch_handlers: package, classname = handler_name.rsplit('.', 1) try: - handler_class = getattr(importlib.import_module(package), classname) + handler_class = getattr( + importlib.import_module(package), + classname) plugin_list.append(handler_class()) except (ImportError, AttributeError): # Skip missing plugins so that they can be ommitted from # installation if desired - log("FetchHandler {} not found, skipping plugin".format(handler_name)) + log("FetchHandler {} not found, skipping plugin".format( + handler_name)) return plugin_list diff --git a/revision b/revision index b661fff6..a1e0432c 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -311 +312 diff --git a/templates/grizzly/nova.conf b/templates/grizzly/nova.conf new file mode 100644 index 00000000..67c63a6a --- /dev/null +++ b/templates/grizzly/nova.conf @@ -0,0 +1,114 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +[DEFAULT] +dhcpbridge_flagfile=/etc/nova/nova.conf +dhcpbridge=/usr/bin/nova-dhcpbridge +logdir=/var/log/nova +state_path=/var/lib/nova +lock_path=/var/lock/nova +force_dhcp_release=True +iscsi_helper=tgtadm +libvirt_use_virtio_for_bridges=True +connection_type=libvirt +root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf +verbose=True +ec2_private_dns_show_ip=True +api_paste_config=/etc/nova/api-paste.ini +volumes_path=/var/lib/nova/volumes +enabled_apis=ec2,osapi_compute,metadata +auth_strategy=keystone +compute_driver=libvirt.LibvirtDriver +{% if keystone_ec2_url -%} +keystone_ec2_url = {{ keystone_ec2_url }} +{% endif -%} + +{% if database_host -%} +sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +{% endif -%} + +{% if rabbitmq_host or rabbitmq_hosts -%} +rabbit_userid = {{ rabbitmq_user }} +rabbit_password = {{ rabbitmq_password }} +rabbit_virtual_host = {{ rabbitmq_virtual_host }} +{% if rabbitmq_hosts -%} +rabbit_hosts = {{ rabbitmq_hosts }} +{% if rabbitmq_ha_queues -%} +rabbit_ha_queues = true +rabbit_durable_queues = false +{% endif %} +{% else %} +rabbit_host = {{ rabbitmq_host }} +{% endif -%} +{% endif -%} + +{% if glance_api_servers -%} +glance_api_servers = {{ glance_api_servers }} +{% endif -%} + +{% if rbd_pool -%} +rbd_pool = {{ rbd_pool }} +rbd_user = {{ rbd_user }} +rbd_secret_uuid = {{ rbd_secret_uuid }} +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'ovs' -%} +libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver +libvirt_user_virtio_for_bridges = True +{% if neutron_security_groups -%} +security_group_api = {{ network_manager }} +nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} +{% if external_network -%} +default_floating_pool = {{ external_network }} +{% endif -%} +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'nvp' -%} +security_group_api = neutron +nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% if external_network -%} +default_floating_pool = {{ external_network }} +{% endif -%} +{% endif -%} + +{% if network_manager_config -%} +{% for key, value in network_manager_config.iteritems() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if network_manager and network_manager == 'quantum' -%} +network_api_class = nova.network.quantumv2.api.API +{% elif network_manager and network_manager == 'neutron' -%} +network_api_class = nova.network.neutronv2.api.API +{% else -%} +network_manager = nova.network.manager.FlatDHCPManager +{% endif -%} + +{% if default_floating_pool -%} +default_floating_pool = {{ default_floating_pool }} +{% endif -%} + +{% if volume_service -%} +volume_api_class=nova.volume.cinder.API +{% endif -%} + +{% if user_config_flags -%} +{% for key, value in user_config_flags.iteritems() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if listen_ports -%} +{% for key, value in listen_ports.iteritems() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if sections and 'DEFAULT' in sections -%} +{% for key, value in sections['DEFAULT'] -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} diff --git a/templates/grizzly/quantum.conf b/templates/grizzly/quantum.conf new file mode 100644 index 00000000..f763d38a --- /dev/null +++ b/templates/grizzly/quantum.conf @@ -0,0 +1,55 @@ +# grizzly +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +[DEFAULT] +state_path = /var/lib/quantum +lock_path = $state_path/lock +bind_host = 0.0.0.0 +{% if neutron_bind_port -%} +bind_port = {{ neutron_bind_port }} +{% else -%} +bind_port = 9696 +{% endif -%} +{% if core_plugin -%} +core_plugin = {{ core_plugin }} +{% endif -%} +api_paste_config = /etc/quantum/api-paste.ini +auth_strategy = keystone +control_exchange = quantum +notification_driver = quantum.openstack.common.notifier.rpc_notifier +default_notification_level = INFO +notification_topics = notifications +{% if rabbitmq_host or rabbitmq_hosts -%} +rabbit_userid = {{ rabbitmq_user }} +rabbit_password = {{ rabbitmq_password }} +rabbit_virtual_host = {{ rabbitmq_virtual_host }} +{% if rabbitmq_hosts -%} +rabbit_hosts = {{ rabbitmq_hosts }} +{% if rabbitmq_ha_queues -%} +rabbit_ha_queues = true +rabbit_durable_queues = false +{% endif %} +{% else %} +rabbit_host = {{ rabbitmq_host }} +{% endif -%} +{% endif -%} +{% if neutron_security_groups -%} +allow_overlapping_ips = True +{% endif -%} + + +[QUOTAS] +quota_driver = quantum.db.quota_db.DbQuotaDriver +{% if neutron_security_groups -%} +quota_items = network,subnet,port,security_group,security_group_rule +{% endif -%} + +[DEFAULT_SERVICETYPE] + +[AGENT] +root_helper = sudo quantum-rootwrap /etc/quantum/rootwrap.conf + +[keystone_authtoken] +# auth_token middleware currently set in /etc/quantum/api-paste.ini diff --git a/templates/havana/neutron.conf b/templates/havana/neutron.conf index 0bfb440d..a3d3b735 100644 --- a/templates/havana/neutron.conf +++ b/templates/havana/neutron.conf @@ -20,11 +20,19 @@ core_plugin = {{ core_plugin }} allow_overlapping_ips = True neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver {% endif -%} -{% if rabbitmq_host -%} -rabbit_host = {{ rabbitmq_host }} +{% if rabbitmq_host or rabbitmq_hosts -%} rabbit_userid = {{ rabbitmq_user }} rabbit_password = {{ rabbitmq_password }} rabbit_virtual_host = {{ rabbitmq_virtual_host }} +{% if rabbitmq_hosts -%} +rabbit_hosts = {{ rabbitmq_hosts }} +{% if rabbitmq_ha_queues -%} +rabbit_ha_queues = true +rabbit_durable_queues = false +{% endif %} +{% else %} +rabbit_host = {{ rabbitmq_host }} +{% endif -%} {% endif -%} [quotas]