diff --git a/hooks/lxd-relation-changed b/hooks/lxd-relation-changed new file mode 120000 index 00000000..3ba0bdea --- /dev/null +++ b/hooks/lxd-relation-changed @@ -0,0 +1 @@ +nova_compute_hooks.py \ No newline at end of file diff --git a/hooks/lxd-relation-joined b/hooks/lxd-relation-joined new file mode 120000 index 00000000..3ba0bdea --- /dev/null +++ b/hooks/lxd-relation-joined @@ -0,0 +1 @@ +nova_compute_hooks.py \ No newline at end of file diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index 889a0df5..c43b07b4 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,12 +68,16 @@ from nova_compute_utils import ( assert_charm_supports_ipv6, manage_ovs, install_hugepages, + REQUIRED_INTERFACES, + check_optional_relations, ) from charmhelpers.contrib.network.ip import ( get_ipv6_addr ) +from charmhelpers.core.unitdata import kv + from nova_compute_context import ( CEPH_SECRET_UUID, assert_libvirt_imagebackend_allowed @@ -86,28 +92,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')) elif not config('action-managed-upgrade'): if openstack_upgrade_available('nova-common'): + status_set('maintenance', 'Running openstack upgrade') do_openstack_upgrade(CONFIGS) sysctl_dict = config('sysctl') @@ -117,11 +133,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: @@ -148,6 +166,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 +176,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 +193,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 +210,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 +224,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 +235,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 +246,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(): @@ -257,8 +289,11 @@ def compute_changed(): @hooks.hook('ceph-relation-joined') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def ceph_joined(): + status_set('maintenance', 'Installing apt packages') apt_install(filter_installed_packages(['ceph-common']), fatal=True) # Bug 1427660 service_restart('libvirt-bin') @@ -272,6 +307,8 @@ def get_ceph_request(): @hooks.hook('ceph-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def ceph_changed(): if 'ceph' not in CONFIGS.complete_contexts(): @@ -306,6 +343,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 +355,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 +365,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 +381,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 +391,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) @@ -365,6 +411,8 @@ def update_nrpe_config(): @hooks.hook('neutron-plugin-relation-changed') +@os_workload_status(CONFIGS, REQUIRED_INTERFACES, + charm_func=check_optional_relations) @restart_on_change(restart_map()) def neutron_plugin_changed(): settings = relation_get() @@ -376,6 +424,22 @@ def neutron_plugin_changed(): CONFIGS.write(NOVA_CONF) +@hooks.hook('lxd-relation-joined') +def lxd_joined(relid=None): + relation_set(relation_id=relid, + user='nova') + + +@hooks.hook('lxd-relation-changed') +def lxc_changed(): + nonce = relation_get('nonce') + db = kv() + if nonce and db.get('lxd-nonce') != nonce: + db.set('lxd-nonce', nonce) + configure_lxd(user='nova') + service_restart('nova-compute') + + def main(): try: hooks.execute(sys.argv) diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index e0a35998..2f2bea9f 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -5,7 +5,11 @@ import subprocess from base64 import b64decode from copy import deepcopy -from subprocess import check_call, check_output, CalledProcessError +from subprocess import ( + check_call, + check_output, + CalledProcessError +) from charmhelpers.fetch import ( apt_update, @@ -32,6 +36,7 @@ from charmhelpers.core.hookenv import ( relation_get, DEBUG, INFO, + status_get, ) from charmhelpers.core.templating import render @@ -48,7 +53,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 ( @@ -131,6 +137,7 @@ GIT_PACKAGE_BLACKLIST = [ 'nova-compute', 'nova-compute-kvm', 'nova-compute-lxc', + 'nova-compute-lxd', 'nova-compute-qemu', 'nova-compute-uml', 'nova-compute-xen', @@ -248,6 +255,13 @@ 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 = { + 'messaging': ['amqp', 'zeromq-configuration'], + 'image': ['image-service'], +} + def resource_map(): ''' @@ -582,20 +596,12 @@ def create_libvirt_secret(secret_file, secret_uuid, key): def configure_lxd(user='nova'): ''' Configure lxd use for nova user ''' - if lsb_release()['DISTRIB_CODENAME'].lower() < "vivid": - raise Exception("LXD is not supported for Ubuntu " - "versions less than 15.04 (vivid)") + if not git_install_requested(): + if lsb_release()['DISTRIB_CODENAME'].lower() < "vivid": + raise Exception("LXD is not supported for Ubuntu " + "versions less than 15.04 (vivid)") - configure_subuid(user='nova') - configure_lxd_daemon(user='nova') - - service_restart('nova-compute') - - -def configure_lxd_daemon(user): - add_user_to_group(user, 'lxd') - service_restart('lxd') - # NOTE(jamespage): Call list function to initialize cert + configure_subuid(user) lxc_list(user) @@ -827,3 +833,21 @@ 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): + required_interfaces = {} + if relation_ids('ceph'): + required_interfaces['storage-backend'] = ['ceph'] + + if relation_ids('neutron-plugin'): + required_interfaces['neutron-plugin'] = ['neutron-plugin'] + + if relation_ids('shared-db') or relation_ids('pgsql-db'): + required_interfaces['database'] = ['shared-db', 'pgsql-db'] + + if required_interfaces: + set_os_workload_status(configs, required_interfaces) + return status_get() + else: + return 'unknown', 'No optional relations' diff --git a/metadata.yaml b/metadata.yaml index 1958074c..d6bb67ec 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -24,6 +24,9 @@ requires: interface: glance ceph: interface: ceph-client + lxd: + interface: containers + scope: container nova-ceilometer: interface: nova-ceilometer scope: container diff --git a/templates/git/nova-compute-lxd.conf b/templates/git/nova-compute-lxd.conf new file mode 100644 index 00000000..94ce84b7 --- /dev/null +++ b/templates/git/nova-compute-lxd.conf @@ -0,0 +1,2 @@ +[DEFAULT] +compute_driver=nclxd.nova.virt.lxd.LXDDriver diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py index aca839a4..f38fbc23 100644 --- a/unit_tests/test_nova_compute_hooks.py +++ b/unit_tests/test_nova_compute_hooks.py @@ -82,7 +82,7 @@ class NovaComputeRelationsTests(CharmTestCase): self.configure_installation_source.assert_called_with(repo) self.assertTrue(self.apt_update.called) self.apt_install.assert_called_with(['foo', 'bar'], fatal=True) - self.execd_preinstall.assert_called() + self.assertTrue(self.execd_preinstall.called) def test_install_hook_git(self): self.git_install_requested.return_value = True @@ -107,7 +107,7 @@ class NovaComputeRelationsTests(CharmTestCase): self.assertTrue(self.apt_update.called) self.apt_install.assert_called_with(['foo', 'bar'], fatal=True) self.git_install.assert_called_with(projects_yaml) - self.execd_preinstall.assert_called() + self.assertTrue(self.execd_preinstall.called) def test_config_changed_with_upgrade(self): self.git_install_requested.return_value = False @@ -194,7 +194,7 @@ class NovaComputeRelationsTests(CharmTestCase): self.git_install_requested.return_value = False self.test_config.set('sysctl', '{ kernel.max_pid : "1337" }') hooks.config_changed() - self.create_sysctl.assert_called() + self.assertTrue(self.create_sysctl.called) @patch.object(hooks, 'config_value_changed') def test_config_changed_git(self, config_val_changed): @@ -235,7 +235,7 @@ class NovaComputeRelationsTests(CharmTestCase): self.migration_enabled.return_value = False self.is_relation_made.return_value = True hooks.config_changed() - self.update_nrpe_config.assert_called_once() + self.assertTrue(self.update_nrpe_config.called) def test_amqp_joined(self): hooks.amqp_joined() @@ -378,7 +378,6 @@ class NovaComputeRelationsTests(CharmTestCase): def test_compute_joined_no_migration_no_resize(self): self.migration_enabled.return_value = False hooks.compute_joined() - self.relation_set.assertCalledWith(hostname='arm') self.assertFalse(self.relation_set.called) def test_compute_joined_with_ssh_migration(self): diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index 878045ab..e367df28 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -463,37 +463,28 @@ class NovaComputeUtilsTests(CharmTestCase): compute_context.CEPH_SECRET_UUID, '--base64', key]) - @patch.object(utils, 'check_call') - @patch.object(utils, 'check_output') - def test_configure_lxd_daemon(self, _check_output, _check_call): - self.test_config.set('virt-type', 'lxd') - utils.configure_lxd_daemon('nova') - self.add_user_to_group.assert_called_with('nova', 'lxd') - self.service_restart.assert_called_with('lxd') - _check_output.assert_called_wth(['sudo', '-u', 'nova', 'lxc', 'list']) - - @patch.object(utils, 'configure_lxd_daemon') + @patch.object(utils, 'lxc_list') @patch.object(utils, 'configure_subuid') - def test_configure_lxd_vivid(self, _configure_subuid, - _configure_lxd_daemon): + def test_configure_lxd_vivid(self, _configure_subuid, _lxc_list): self.lsb_release.return_value = { 'DISTRIB_CODENAME': 'vivid' } utils.configure_lxd('nova') - _configure_subuid.assert_called_with(user='nova') - _configure_lxd_daemon.assert_called_with(user='nova') + _configure_subuid.assert_called_with('nova') + _lxc_list.assert_called_with('nova') - @patch.object(utils, 'configure_lxd_daemon') + @patch.object(utils, 'git_install_requested') + @patch.object(utils, 'lxc_list') @patch.object(utils, 'configure_subuid') - def test_configure_lxd_pre_vivid(self, _configure_subuid, - _configure_lxd_daemon): + def test_configure_lxd_pre_vivid(self, _configure_subuid, _lxc_list, + _git_install): + _git_install.return_value = False self.lsb_release.return_value = { 'DISTRIB_CODENAME': 'trusty' } with self.assertRaises(Exception): utils.configure_lxd('nova') self.assertFalse(_configure_subuid.called) - self.assertFalse(_configure_lxd_daemon.called) def test_enable_nova_metadata(self): class DummyContext(): 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(): '''