From 783d5ccc8550b8ec0a4c6c3d390b728f04f618fa Mon Sep 17 00:00:00 2001 From: David Ames Date: Fri, 25 Sep 2015 15:12:01 -0700 Subject: [PATCH] Workload Status --- .../contrib/openstack/templating.py | 3 +- hooks/charmhelpers/contrib/openstack/utils.py | 56 ++++++++++++++++++- hooks/nova_compute_hooks.py | 45 +++++++++++++++ hooks/nova_compute_utils.py | 21 ++++++- unit_tests/test_utils.py | 3 + 5 files changed, 125 insertions(+), 3 deletions(-) diff --git a/hooks/charmhelpers/contrib/openstack/templating.py b/hooks/charmhelpers/contrib/openstack/templating.py index 0813719a..e5e3cb1b 100644 --- a/hooks/charmhelpers/contrib/openstack/templating.py +++ b/hooks/charmhelpers/contrib/openstack/templating.py @@ -18,7 +18,7 @@ import os import six -from charmhelpers.fetch import apt_install +from charmhelpers.fetch import apt_install, apt_update from charmhelpers.core.hookenv import ( log, ERROR, @@ -29,6 +29,7 @@ from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES try: from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions except ImportError: + apt_update(fatal=True) apt_install('python-jinja2', fatal=True) from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 47a4c22f..eefcf08b 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -25,6 +25,7 @@ import sys import re import six +import traceback import yaml from charmhelpers.contrib.network import ip @@ -34,6 +35,8 @@ from charmhelpers.core import ( ) from charmhelpers.core.hookenv import ( + action_fail, + action_set, config, log as juju_log, charm_dir, @@ -51,7 +54,8 @@ from charmhelpers.contrib.storage.linux.lvm import ( ) from charmhelpers.contrib.network.ip import ( - get_ipv6_addr + get_ipv6_addr, + is_ipv6, ) from charmhelpers.contrib.python.packages import ( @@ -516,6 +520,12 @@ def sync_db_with_multi_ipv6_addresses(database, database_user, relation_prefix=None): hosts = get_ipv6_addr(dynamic_only=False) + if config('vip'): + vips = config('vip').split() + for vip in vips: + if vip and is_ipv6(vip): + hosts.append(vip) + kwargs = {'database': database, 'username': database_user, 'hostname': json.dumps(hosts)} @@ -921,3 +931,47 @@ def incomplete_relation_data(configs, required_interfaces): for i in incomplete_relations: incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) return incomplete_context_data + + +def do_action_openstack_upgrade(package, upgrade_callback, configs): + """Perform action-managed OpenStack upgrade. + + Upgrades packages to the configured openstack-origin version and sets + the corresponding action status as a result. + + If the charm was installed from source we cannot upgrade it. + For backwards compatibility a config flag (action-managed-upgrade) must + be set for this code to run, otherwise a full service level upgrade will + fire on config-changed. + + @param package: package name for determining if upgrade available + @param upgrade_callback: function callback to charm's upgrade function + @param configs: templating object derived from OSConfigRenderer class + + @return: True if upgrade successful; False if upgrade failed or skipped + """ + ret = False + + if git_install_requested(): + action_set({'outcome': 'installed from source, skipped upgrade.'}) + else: + if openstack_upgrade_available(package): + if config('action-managed-upgrade'): + juju_log('Upgrading OpenStack release') + + try: + upgrade_callback(configs=configs) + action_set({'outcome': 'success, upgrade completed.'}) + ret = True + except: + action_set({'outcome': 'upgrade failed, see traceback.'}) + action_set({'traceback': traceback.format_exc()}) + action_fail('do_openstack_upgrade resulted in an ' + 'unexpected error') + else: + action_set({'outcome': 'action-managed-upgrade config is ' + 'False, skipped upgrade.'}) + else: + action_set({'outcome': 'no upgrade available.'}) + + return ret diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index df58bb10..50c9dd77 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -13,6 +13,7 @@ from charmhelpers.core.hookenv import ( service_name, unit_get, UnregisteredHookError, + status_set, ) from charmhelpers.core.host import ( restart_on_change, @@ -32,6 +33,7 @@ from charmhelpers.contrib.openstack.utils import ( git_install_requested, openstack_upgrade_available, os_requires_version, + os_workload_status, ) from charmhelpers.contrib.storage.linux.ceph import ( @@ -66,6 +68,8 @@ from nova_compute_utils import ( assert_charm_supports_ipv6, manage_ovs, install_hugepages, + REQUIRED_INTERFACES, + check_optional_relations, ) from charmhelpers.contrib.network.ip import ( @@ -86,28 +90,38 @@ CONFIGS = register_configs() @hooks.hook('install.real') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def install(): + status_set('maintenance', 'Executing pre-install') execd_preinstall() configure_installation_source(config('openstack-origin')) + status_set('maintenance', 'Installing apt packages') apt_update() apt_install(determine_packages(), fatal=True) + status_set('maintenance', 'Git install') git_install(config('openstack-origin-git')) @hooks.hook('config-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def config_changed(): if config('prefer-ipv6'): + status_set('maintenance', 'configuring ipv6') assert_charm_supports_ipv6() global CONFIGS if git_install_requested(): if config_value_changed('openstack-origin-git'): + status_set('maintenance', 'Running Git install') git_install(config('openstack-origin-git')) else: if openstack_upgrade_available('nova-common'): + status_set('maintenance', 'Running openstack upgrade') CONFIGS = do_openstack_upgrade() sysctl_dict = config('sysctl') @@ -117,11 +131,13 @@ def config_changed(): if migration_enabled() and config('migration-auth-type') == 'ssh': # Check-in with nova-c-c and register new ssh key, if it has just been # generated. + status_set('maintenance', 'SSH key exchange') initialize_ssh_keys() import_authorized_keys() if config('enable-resize') is True: enable_shell(user='nova') + status_set('maintenance', 'SSH key exchange') initialize_ssh_keys(user='nova') import_authorized_keys(user='nova', prefix='nova') else: @@ -132,6 +148,7 @@ def config_changed(): fix_path_ownership(fp, user='nova') if config('virt-type').lower() == 'lxd': + status_set('maintenance', 'Configure LXD') configure_lxd(user='nova') [compute_joined(rid) for rid in relation_ids('cloud-compute')] @@ -148,6 +165,8 @@ def config_changed(): @hooks.hook('amqp-relation-joined') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def amqp_joined(relation_id=None): relation_set(relation_id=relation_id, username=config('rabbit-user'), @@ -156,6 +175,8 @@ def amqp_joined(relation_id=None): @hooks.hook('amqp-relation-changed') @hooks.hook('amqp-relation-departed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def amqp_changed(): if 'amqp' not in CONFIGS.complete_contexts(): @@ -171,6 +192,8 @@ def amqp_changed(): @hooks.hook('shared-db-relation-joined') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def db_joined(rid=None): if is_relation_made('pgsql-db'): # error, postgresql is used @@ -186,6 +209,8 @@ def db_joined(rid=None): @hooks.hook('pgsql-db-relation-joined') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def pgsql_db_joined(): if is_relation_made('shared-db'): # raise error @@ -198,6 +223,8 @@ def pgsql_db_joined(): @hooks.hook('shared-db-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def db_changed(): if 'shared-db' not in CONFIGS.complete_contexts(): @@ -207,6 +234,8 @@ def db_changed(): @hooks.hook('pgsql-db-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def postgresql_db_changed(): if 'pgsql-db' not in CONFIGS.complete_contexts(): @@ -216,6 +245,8 @@ def postgresql_db_changed(): @hooks.hook('image-service-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def image_service_changed(): if 'image-service' not in CONFIGS.complete_contexts(): @@ -258,7 +289,10 @@ def compute_changed(): @hooks.hook('ceph-relation-joined') @restart_on_change(restart_map()) +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def ceph_joined(): + status_set('maintenance', 'Installing apt packages') apt_install(filter_installed_packages(['ceph-common']), fatal=True) # Bug 1427660 service_restart('libvirt-bin') @@ -273,6 +307,8 @@ def get_ceph_request(): @hooks.hook('ceph-relation-changed') @restart_on_change(restart_map()) +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def ceph_changed(): if 'ceph' not in CONFIGS.complete_contexts(): log('ceph relation incomplete. Peer not ready?') @@ -306,6 +342,8 @@ def ceph_changed(): @hooks.hook('ceph-relation-broken') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) def ceph_broken(): service = service_name() delete_keyring(service=service) @@ -316,6 +354,8 @@ def ceph_broken(): 'image-service-relation-broken', 'shared-db-relation-broken', 'pgsql-db-relation-broken') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def relation_broken(): CONFIGS.write_all() @@ -324,6 +364,7 @@ def relation_broken(): @hooks.hook('upgrade-charm') def upgrade_charm(): # NOTE: ensure psutil install for hugepages configuration + status_set('maintenance', 'Installing apt packages') apt_install(filter_installed_packages(['python-psutil'])) for r_id in relation_ids('amqp'): amqp_joined(relation_id=r_id) @@ -339,6 +380,8 @@ def nova_ceilometer_relation_changed(): @hooks.hook('zeromq-configuration-relation-joined') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @os_requires_version('kilo', 'nova-common') def zeromq_configuration_relation_joined(relid=None): relation_set(relation_id=relid, @@ -347,6 +390,8 @@ def zeromq_configuration_relation_joined(relid=None): @hooks.hook('zeromq-configuration-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def zeromq_configuration_relation_changed(): CONFIGS.write(NOVA_CONF) diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index 15e7a535..87126dd2 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -32,6 +32,7 @@ from charmhelpers.core.hookenv import ( relation_get, DEBUG, INFO, + status_get, ) from charmhelpers.core.templating import render @@ -48,7 +49,8 @@ from charmhelpers.contrib.openstack.utils import ( git_src_dir, git_pip_venv_dir, git_yaml_value, - os_release + os_release, + set_os_workload_status, ) from charmhelpers.contrib.python.packages import ( @@ -248,6 +250,14 @@ LIBVIRT_URIS = { 'lxc': 'lxc:///', } +# The interface is said to be satisfied if anyone of the interfaces in the +# list has a complete context. +REQUIRED_INTERFACES = { + 'database': ['shared-db', 'pgsql-db'], + 'message': ['amqp', 'zeromq-configuration'], + 'image': ['image-service'], +} + def resource_map(): ''' @@ -829,3 +839,12 @@ def install_hugepages(): ) subprocess.check_call('/etc/init.d/qemu-hugefsdir') subprocess.check_call(['update-rc.d', 'qemu-hugefsdir', 'defaults']) + + +def check_optional_relations(configs): + if relation_ids('ceph'): + required_interfaces = {'image-backend': 'ceph'} + set_os_workload_status(configs, required_interfaces) + return status_get() + else: + return 'unknown', 'No optional relations' diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py index c9c7bace..cc80b485 100644 --- a/unit_tests/test_utils.py +++ b/unit_tests/test_utils.py @@ -6,6 +6,9 @@ import yaml from contextlib import contextmanager from mock import patch, MagicMock +patch('charmhelpers.contrib.openstack.utils.set_os_workload_status').start() +patch('charmhelpers.core.hookenv.status_set').start() + def load_config(): '''