diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml index 84a8039..13a6fdb 100644 --- a/charm-helpers-sync.yaml +++ b/charm-helpers-sync.yaml @@ -5,4 +5,7 @@ include: - fetch - contrib.openstack|inc=* - contrib.storage + - contrib.hahelpers: + - apache + - cluster - payload.execd diff --git a/config.yaml b/config.yaml index ab035e2..140e8da 100644 --- a/config.yaml +++ b/config.yaml @@ -15,7 +15,7 @@ options: provide a later version of OpenStack will trigger a software upgrade. rabbit-user: - default: nova + default: heat type: string description: Username used to access rabbitmq queue rabbit-vhost: @@ -23,10 +23,10 @@ options: type: string decsription: Rabbitmq vhost database-user: - default: nova + default: heat type: string description: Username for database access database: - default: nova + default: heat type: string description: Database name diff --git a/hooks/charmhelpers/contrib/hahelpers/__init__.py b/hooks/charmhelpers/contrib/hahelpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hooks/charmhelpers/contrib/hahelpers/apache.py b/hooks/charmhelpers/contrib/hahelpers/apache.py new file mode 100644 index 0000000..3208a85 --- /dev/null +++ b/hooks/charmhelpers/contrib/hahelpers/apache.py @@ -0,0 +1,58 @@ +# +# Copyright 2012 Canonical Ltd. +# +# This file is sourced from lp:openstack-charm-helpers +# +# Authors: +# James Page +# Adam Gandelman +# + +import subprocess + +from charmhelpers.core.hookenv import ( + config as config_get, + relation_get, + relation_ids, + related_units as relation_list, + log, + INFO, +) + + +def get_cert(): + cert = config_get('ssl_cert') + key = config_get('ssl_key') + if not (cert and key): + log("Inspecting identity-service relations for SSL certificate.", + level=INFO) + cert = key = None + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + if not cert: + cert = relation_get('ssl_cert', + rid=r_id, unit=unit) + if not key: + key = relation_get('ssl_key', + rid=r_id, unit=unit) + return (cert, key) + + +def get_ca_cert(): + ca_cert = None + log("Inspecting identity-service relations for CA SSL certificate.", + level=INFO) + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + if not ca_cert: + ca_cert = relation_get('ca_cert', + rid=r_id, unit=unit) + return ca_cert + + +def install_ca_cert(ca_cert): + if ca_cert: + with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt', + 'w') as crt: + crt.write(ca_cert) + subprocess.check_call(['update-ca-certificates', '--fresh']) diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py new file mode 100644 index 0000000..074855f --- /dev/null +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -0,0 +1,183 @@ +# +# Copyright 2012 Canonical Ltd. +# +# Authors: +# James Page +# Adam Gandelman +# + +import subprocess +import os + +from socket import gethostname as get_unit_hostname + +from charmhelpers.core.hookenv import ( + log, + relation_ids, + related_units as relation_list, + relation_get, + config as config_get, + INFO, + ERROR, + unit_get, +) + + +class HAIncompleteConfig(Exception): + pass + + +def is_clustered(): + for r_id in (relation_ids('ha') or []): + for unit in (relation_list(r_id) or []): + clustered = relation_get('clustered', + rid=r_id, + unit=unit) + if clustered: + return True + return False + + +def is_leader(resource): + cmd = [ + "crm", "resource", + "show", resource + ] + try: + status = subprocess.check_output(cmd) + except subprocess.CalledProcessError: + return False + else: + if get_unit_hostname() in status: + return True + else: + return False + + +def peer_units(): + peers = [] + for r_id in (relation_ids('cluster') or []): + for unit in (relation_list(r_id) or []): + peers.append(unit) + return peers + + +def oldest_peer(peers): + local_unit_no = int(os.getenv('JUJU_UNIT_NAME').split('/')[1]) + for peer in peers: + remote_unit_no = int(peer.split('/')[1]) + if remote_unit_no < local_unit_no: + return False + return True + + +def eligible_leader(resource): + if is_clustered(): + if not is_leader(resource): + log('Deferring action to CRM leader.', level=INFO) + return False + else: + peers = peer_units() + if peers and not oldest_peer(peers): + log('Deferring action to oldest service unit.', level=INFO) + return False + return True + + +def https(): + ''' + Determines whether enough data has been provided in configuration + or relation data to configure HTTPS + . + returns: boolean + ''' + if config_get('use-https') == "yes": + return True + if config_get('ssl_cert') and config_get('ssl_key'): + return True + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + rel_state = [ + relation_get('https_keystone', rid=r_id, unit=unit), + relation_get('ssl_cert', rid=r_id, unit=unit), + relation_get('ssl_key', rid=r_id, unit=unit), + relation_get('ca_cert', rid=r_id, unit=unit), + ] + # NOTE: works around (LP: #1203241) + if (None not in rel_state) and ('' not in rel_state): + return True + return False + + +def determine_api_port(public_port): + ''' + Determine correct API server listening port based on + existence of HTTPS reverse proxy and/or haproxy. + + public_port: int: standard public port for given service + + returns: int: the correct listening port for the API service + ''' + i = 0 + if len(peer_units()) > 0 or is_clustered(): + i += 1 + if https(): + i += 1 + return public_port - (i * 10) + + +def determine_haproxy_port(public_port): + ''' + Description: Determine correct proxy listening port based on public IP + + existence of HTTPS reverse proxy. + + public_port: int: standard public port for given service + + returns: int: the correct listening port for the HAProxy service + ''' + i = 0 + if https(): + i += 1 + return public_port - (i * 10) + + +def get_hacluster_config(): + ''' + Obtains all relevant configuration from charm configuration required + for initiating a relation to hacluster: + + ha-bindiface, ha-mcastport, vip, vip_iface, vip_cidr + + returns: dict: A dict containing settings keyed by setting name. + raises: HAIncompleteConfig if settings are missing. + ''' + settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'vip_iface', 'vip_cidr'] + conf = {} + for setting in settings: + conf[setting] = config_get(setting) + missing = [] + [missing.append(s) for s, v in conf.iteritems() if v is None] + if missing: + log('Insufficient config data to configure hacluster.', level=ERROR) + raise HAIncompleteConfig + return conf + + +def canonical_url(configs, vip_setting='vip'): + ''' + Returns the correct HTTP URL to this host given the state of HTTPS + configuration and hacluster. + + :configs : OSTemplateRenderer: A config tempating object to inspect for + a complete https context. + :vip_setting: str: Setting in charm config that specifies + VIP address. + ''' + scheme = 'http' + if 'https' in configs.complete_contexts(): + scheme = 'https' + if is_clustered(): + addr = config_get(vip_setting) + else: + addr = unit_get('private-address') + return '%s://%s' % (scheme, addr) diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index d66afd7..4c86d03 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -210,6 +210,7 @@ def import_key(keyid): def configure_installation_source(rel): + juju_log("in configure %s" % rel) '''Configure apt installation source.''' if rel == 'distro': return @@ -219,6 +220,7 @@ def configure_installation_source(rel): f.write(DISTRO_PROPOSED % ubuntu_rel) elif rel[:4] == "ppa:": src = rel + juju_log("add apt %s" % src) subprocess.check_call(["add-apt-repository", "-y", src]) elif rel[:3] == "deb": l = len(rel.split('|')) diff --git a/hooks/heat_context.py b/hooks/heat_context.py index e0324f8..3ce118a 100644 --- a/hooks/heat_context.py +++ b/hooks/heat_context.py @@ -15,7 +15,7 @@ class IdentityServiceContext(context.IdentityServiceContext): return # the ec2 api needs to know the location of the keystone ec2 - # tokens endpoint, set in nova.conf + # tokens endpoint, set in heat.conf ec2_tokens = 'http://%s:%s/v2.0/ec2tokens' % (ctxt['service_host'], ctxt['service_port']) ctxt['keystone_ec2_url'] = ec2_tokens diff --git a/hooks/hooks.py b/hooks/hooks.py index 170144d..74c2f3f 100755 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -40,7 +40,6 @@ from charmhelpers.fetch import ( from charmhelpers.contrib.openstack.utils import ( configure_installation_source, - openstack_upgrade_available, ) from utils import ( @@ -49,13 +48,8 @@ from utils import ( determine_endpoints, determine_packages, determine_ports, - do_openstack_upgrade, keystone_ca_cert_b64, save_script_rc, - ssh_compute_add, - ssh_compute_remove, - ssh_known_hosts_b64, - ssh_authorized_keys_b64, register_configs, restart_map, HEAT_CONF @@ -106,9 +100,9 @@ def amqp_changed(): @hooks.hook('shared-db-relation-joined') def db_joined(): - relation_set(nova_database=config('database'), - nova_username=config('database-user'), - nova_hostname=unit_get('private-address')) + relation_set(heat_database=config('database'), + heat_username=config('database-user'), + heat_hostname=unit_get('private-address')) @hooks.hook('shared-db-relation-changed') @@ -118,6 +112,7 @@ def db_changed(): log('shared-db relation incomplete. Peer not ready?') return CONFIGS.write(HEAT_CONF) + check_call(['heat-manage', 'db_sync']) @hooks.hook('identity-service-relation-joined') diff --git a/hooks/utils.py b/hooks/utils.py index c4d4c10..7583591 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -66,7 +66,7 @@ BASE_RESOURCE_MAP = OrderedDict([ (HEAT_CONF, { 'services': BASE_SERVICES, 'contexts': [context.AMQPContext(), - context.SharedDBContext(relation_prefix='nova'), + context.SharedDBContext(relation_prefix='heat'), context.OSConfigFlagContext(), heat_context.IdentityServiceContext()] }), diff --git a/metadata.yaml b/metadata.yaml index ed9b5ff..26a01f2 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -2,9 +2,9 @@ name: heat summary: OpenStack orchestration engine maintainer: Yolanda Robla description: | -Heat is the main project in the OpenStack Orchestration program. It implements an -orchestration engine to launch multiple composite cloud applications based on -templates in the form of text files that can be treated like code. + Heat is the main project in the OpenStack Orchestration program. It implements an + orchestration engine to launch multiple composite cloud applications based on + templates in the form of text files that can be treated like code. categories: - openstack provides: diff --git a/templates/heat.conf b/templates/heat.conf index a8c3dad..68c80f4 100644 --- a/templates/heat.conf +++ b/templates/heat.conf @@ -15,63 +15,11 @@ environment_dir=/etc/heat/environment.d # (string value) deferred_auth_method=password -# Subset of trustor roles to be delegated to heat (list value) -trusts_delegated_roles=heat_stack_owner - -# Maximum resources allowed per top-level stack. (integer -# value) -max_resources_per_stack=1000 - -# Maximum number of stacks any one tenant may have active at -# one time. (integer value) -max_stacks_per_tenant=100 - -# Controls how many events will be pruned whenever a stack's -# events exceed max_events_per_stack. Set this lower to keep -# more events at the expense of more frequent purges. (integer -# value) -event_purge_batch_size=10 - -# Maximum events that will be available per stack. Older -# events will be deleted when this is reached. Set to 0 for -# unlimited events per stack. (integer value) -max_events_per_stack=1000 - # Name of the engine node. This can be an opaque identifier.It # is not necessarily a hostname, FQDN, or IP address. (string # value) host=heat -# seconds between running periodic tasks (integer value) -periodic_interval=60 - -# URL of the Heat metadata server (string value) -heat_metadata_server_url= - -# URL of the Heat waitcondition server (string value) -heat_waitcondition_server_url= - -# URL of the Heat cloudwatch server (string value) -heat_watch_server_url= - -# Instance connection to cfn/cw API via https (string value) -instance_connection_is_secure=0 - -# Instance connection to cfn/cw API validate certs if ssl -# (string value) -instance_connection_https_validate_certificates=1 - -# Keystone role for heat template-defined users (string value) -heat_stack_user_role=heat_stack_user - -# Maximum raw byte size of any template. (integer value) -max_template_size=524288 - -# Maximum depth allowed when using nested stacks. (integer -# value) -max_nested_stack_depth=3 - - # # Options defined in heat.common.crypt # @@ -80,33 +28,6 @@ max_nested_stack_depth=3 # (string value) auth_encryption_key=notgood but just long enough i think - -# -# Options defined in heat.common.wsgi -# - -# Maximum raw byte size of JSON request body. Should be larger -# than max_template_size. (integer value) -max_json_body_size=1048576 - - -# -# Options defined in heat.db.api -# - -# The backend to use for db (string value) -db_backend=mysql - - -# -# Options defined in heat.engine.clients -# - -# Fully qualified class name to use as a client backend. -# (string value) -cloud_backend=heat.engine.clients.OpenStackClients - - {% if rabbitmq_host -%} rabbit_host = {{ rabbitmq_host }} rabbit_userid = {{ rabbitmq_user }} @@ -115,15 +36,12 @@ rabbit_virtual_host = {{ rabbitmq_virtual_host }} rabbit_use_ssl = false {% endif -%} +{% if database_host -%} +sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +{% endif -%} + [database] -# -# Options defined in heat.openstack.common.db.api -# - -# The backend to use for db (string value) -backend=mysql - # sql {% if database_host -%} connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}