diff --git a/.bzrignore b/.bzrignore index a2c7a097..421e2bda 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,2 +1,3 @@ bin .coverage +tags diff --git a/Makefile b/Makefile index 94cca8a0..d3b02502 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PYTHON := /usr/bin/env python lint: - @flake8 --exclude hooks/charmhelpers hooks unit_tests tests + @flake8 --exclude hooks/charmhelpers actions hooks unit_tests tests @charm proof unit_test: @@ -24,7 +24,9 @@ test: # raise_status() messages to stderr: # https://bugs.launchpad.net/amulet/+bug/1320357 @juju test -v -p AMULET_HTTP_PROXY --timeout 900 \ - 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse + 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse \ + 16-basic-trusty-icehouse-git 17-basic-trusty-juno \ + 18-basic-trusty-juno-git publish: lint unit_test bzr push lp:charms/nova-compute diff --git a/README.md b/README.md index b14a45ad..d8cfa931 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,102 @@ The following interfaces are provided: - nrpe-external-master - Used to generate Nagios checks. Database --------- +======== Nova compute only requires database access if using nova-network. If using Neutron, no direct database access is required and the shared-db relation need not be added. Networking ----------- +========== This charm support nova-network (legacy) and Neutron networking. Storage -------- +======= This charm supports a number of different storage backends depending on your hypervisor type and storage relations. + +Deploying from source +===================== + +The minimum openstack-origin-git config required to deploy from source is: + + openstack-origin-git: + "repositories: + - {name: requirements, + repository: 'git://git.openstack.org/openstack/requirements', + branch: stable/juno} + - {name: nova, + repository: 'git://git.openstack.org/openstack/nova', + branch: stable/juno}" + +Note that there are only two 'name' values the charm knows about: 'requirements' +and 'nova'. These repositories must correspond to these 'name' values. +Additionally, the requirements repository must be specified first and the +nova repository must be specified last. All other repostories are installed +in the order in which they are specified. + +The following is a full list of current tip repos (may not be up-to-date): + + openstack-origin-git: + "repositories: + - {name: requirements, + repository: 'git://git.openstack.org/openstack/requirements', + branch: master} + - {name: oslo-concurrency, + repository: 'git://git.openstack.org/openstack/oslo.concurrency', + branch: master} + - {name: oslo-config, + repository: 'git://git.openstack.org/openstack/oslo.config', + branch: master} + - {name: oslo-context, + repository: 'git://git.openstack.org/openstack/oslo.context.git', + branch: master} + - {name: oslo-db, + repository: 'git://git.openstack.org/openstack/oslo.db', + branch: master} + - {name: oslo-i18n, + repository: 'git://git.openstack.org/openstack/oslo.i18n', + branch: master} + - {name: oslo-log, + repository: 'git://git.openstack.org/openstack/oslo.log', + branch: master} + - {name: oslo-messaging, + repository: 'git://git.openstack.org/openstack/oslo.messaging.git', + branch: master} + - {name: oslo-middleware, + repository': 'git://git.openstack.org/openstack/oslo.middleware.git', + branch: master} + - {name: oslo-rootwrap', + repository: 'git://git.openstack.org/openstack/oslo.rootwrap.git', + branch: master} + - {name: oslo-serialization, + repository: 'git://git.openstack.org/openstack/oslo.serialization', + branch: master} + - {name: oslo-utils, + repository: 'git://git.openstack.org/openstack/oslo.utils', + branch: master} + - {name: pbr, + repository: 'git://git.openstack.org/openstack-dev/pbr', + branch: master} + - {name: stevedore, + repository: 'git://git.openstack.org/openstack/stevedore.git', + branch: 'master'} + - {name: sqlalchemy-migrate, + repository: 'git://git.openstack.org/stackforge/sqlalchemy-migrate', + branch: master} + - {name: python-cinderclient, + repository: 'git://git.openstack.org/openstack/python-cinderclient.git', + branch: master} + - {name: python-glanceclient, + repository': 'git://git.openstack.org/openstack/python-glanceclient.git', + branch: master} + - {name: python-neutronlient, + repository': 'git://git.openstack.org/openstack/python-neutronclient.git', + branch: master} + - {name: keystonemiddleware, + repository: 'git://git.openstack.org/openstack/keystonemiddleware', + branch: master} + - {name: nova, + repository: 'git://git.openstack.org/openstack/nova', + branch: master}" diff --git a/actions.yaml b/actions.yaml new file mode 100644 index 00000000..f5b64c38 --- /dev/null +++ b/actions.yaml @@ -0,0 +1,2 @@ +git-reinstall: + description: Reinstall nova-compute from the openstack-origin-git repositories. diff --git a/actions/git-reinstall b/actions/git-reinstall new file mode 120000 index 00000000..ff684984 --- /dev/null +++ b/actions/git-reinstall @@ -0,0 +1 @@ +git_reinstall.py \ No newline at end of file diff --git a/actions/git_reinstall.py b/actions/git_reinstall.py new file mode 100755 index 00000000..8172a04f --- /dev/null +++ b/actions/git_reinstall.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +import sys +import traceback + +sys.path.append('hooks/') + +from charmhelpers.contrib.openstack.utils import ( + git_install_requested, +) + +from charmhelpers.core.hookenv import ( + action_set, + action_fail, + config, +) + +from nova_compute_utils import ( + git_install, +) + +from nova_compute_hooks import ( + config_changed, +) + + +def git_reinstall(): + """Reinstall from source and restart services. + + If the openstack-origin-git config option was used to install openstack + from source git repositories, then this action can be used to reinstall + from updated git repositories, followed by a restart of services.""" + if not git_install_requested(): + action_fail('openstack-origin-git is not configured') + return + + try: + git_install(config('openstack-origin-git')) + except: + action_set({'traceback': traceback.format_exc()}) + action_fail('git-reinstall resulted in an unexpected error') + + +if __name__ == '__main__': + git_reinstall() + config_changed() diff --git a/config.yaml b/config.yaml index 91db1a8a..86530f80 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,12 @@ options: + debug: + default: False + type: boolean + description: "Enable debug logging" + verbose: + default: False + type: boolean + description: "Enable verbose logging" openstack-origin: default: distro type: string @@ -14,6 +22,22 @@ options: Note that updating this setting to a source that is known to provide a later version of OpenStack will trigger a software upgrade. + + Note that when openstack-origin-git is specified, openstack + specific packages will be installed from source rather than + from the openstack-origin repository. + openstack-origin-git: + default: + type: string + description: | + Specifies a YAML-formatted dictionary listing the git + repositories and branches from which to install OpenStack and + its dependencies. + + Note that the installed config files will be determined based on + the OpenStack release of the openstack-origin option. + + For more details see README.md. nova-config: default: /etc/nova/nova.conf type: string diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 45e65790..c9914d0d 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -320,14 +320,15 @@ def db_ssl(rdata, ctxt, ssl_dir): class IdentityServiceContext(OSContextGenerator): - interfaces = ['identity-service'] - def __init__(self, service=None, service_user=None): + def __init__(self, service=None, service_user=None, rel_name='identity-service'): self.service = service self.service_user = service_user + self.rel_name = rel_name + self.interfaces = [self.rel_name] def __call__(self): - log('Generating template context for identity-service', level=DEBUG) + log('Generating template context for ' + self.rel_name, level=DEBUG) ctxt = {} if self.service and self.service_user: @@ -341,7 +342,7 @@ class IdentityServiceContext(OSContextGenerator): ctxt['signing_dir'] = cachedir - for rid in relation_ids('identity-service'): + for rid in relation_ids(self.rel_name): for unit in related_units(rid): rdata = relation_get(rid=rid, unit=unit) serv_host = rdata.get('service_host') @@ -807,6 +808,19 @@ class NeutronContext(OSContextGenerator): return ovs_ctxt + def nuage_ctxt(self): + driver = neutron_plugin_attribute(self.plugin, 'driver', + self.network_manager) + config = neutron_plugin_attribute(self.plugin, 'config', + self.network_manager) + nuage_ctxt = {'core_plugin': driver, + 'neutron_plugin': 'vsp', + 'neutron_security_groups': self.neutron_security_groups, + 'local_ip': unit_private_ip(), + 'config': config} + + return nuage_ctxt + def nvp_ctxt(self): driver = neutron_plugin_attribute(self.plugin, 'driver', self.network_manager) @@ -890,6 +904,8 @@ class NeutronContext(OSContextGenerator): ctxt.update(self.n1kv_ctxt()) elif self.plugin == 'Calico': ctxt.update(self.calico_ctxt()) + elif self.plugin == 'vsp': + ctxt.update(self.nuage_ctxt()) alchemy_flags = config('neutron-alchemy-flags') if alchemy_flags: diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index f8851050..02c92e9c 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -180,6 +180,19 @@ def neutron_plugins(): 'nova-api-metadata']], 'server_packages': ['neutron-server', 'calico-control'], 'server_services': ['neutron-server'] + }, + 'vsp': { + 'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini', + 'driver': 'neutron.plugins.nuage.plugin.NuagePlugin', + 'contexts': [ + context.SharedDBContext(user=config('neutron-database-user'), + database=config('neutron-database'), + relation_prefix='neutron', + ssl_dir=NEUTRON_CONF_DIR)], + 'services': [], + 'packages': [], + 'server_packages': ['neutron-server', 'neutron-plugin-nuage'], + 'server_services': ['neutron-server'] } } if release >= 'icehouse': diff --git a/hooks/charmhelpers/contrib/openstack/templates/git.upstart b/hooks/charmhelpers/contrib/openstack/templates/git.upstart index da94ad12..4bed404b 100644 --- a/hooks/charmhelpers/contrib/openstack/templates/git.upstart +++ b/hooks/charmhelpers/contrib/openstack/templates/git.upstart @@ -9,5 +9,9 @@ respawn exec start-stop-daemon --start --chuid {{ user_name }} \ --chdir {{ start_dir }} --name {{ process_name }} \ --exec {{ executable_name }} -- \ + {% for config_file in config_files -%} --config-file={{ config_file }} \ + {% endfor -%} + {% if log_file -%} --log-file={{ log_file }} + {% endif -%} diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 78c5e2df..5a12c9d6 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -510,8 +510,10 @@ def git_clone_and_install(projects_yaml, core_project): repository: 'git://git.openstack.org/openstack/requirements.git', branch: 'stable/icehouse'} directory: /mnt/openstack-git + http_proxy: http://squid.internal:3128 + https_proxy: https://squid.internal:3128 - The directory key is optional. + The directory, http_proxy, and https_proxy keys are optional. """ global requirements_dir parent_dir = '/mnt/openstack-git' @@ -522,6 +524,12 @@ def git_clone_and_install(projects_yaml, core_project): projects = yaml.load(projects_yaml) _git_validate_projects_yaml(projects, core_project) + if 'http_proxy' in projects.keys(): + os.environ['http_proxy'] = projects['http_proxy'] + + if 'https_proxy' in projects.keys(): + os.environ['https_proxy'] = projects['https_proxy'] + if 'directory' in projects.keys(): parent_dir = projects['directory'] diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index 715dd4c5..86f805f1 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -20,11 +20,13 @@ # Authors: # Charm Helpers Developers +from __future__ import print_function import os import json import yaml import subprocess import sys +import errno from subprocess import CalledProcessError import six @@ -87,7 +89,18 @@ def log(message, level=None): if not isinstance(message, six.string_types): message = repr(message) command += [message] - subprocess.call(command) + # Missing juju-log should not cause failures in unit tests + # Send log output to stderr + try: + subprocess.call(command) + except OSError as e: + if e.errno == errno.ENOENT: + if level: + message = "{}: {}".format(level, message) + message = "juju-log: {}".format(message) + print(message, file=sys.stderr) + else: + raise class Serializable(UserDict): diff --git a/hooks/charmhelpers/core/strutils.py b/hooks/charmhelpers/core/strutils.py index efc4402e..a2a784aa 100644 --- a/hooks/charmhelpers/core/strutils.py +++ b/hooks/charmhelpers/core/strutils.py @@ -33,9 +33,9 @@ def bool_from_string(value): value = value.strip().lower() - if value in ['y', 'yes', 'true', 't']: + if value in ['y', 'yes', 'true', 't', 'on']: return True - elif value in ['n', 'no', 'false', 'f']: + elif value in ['n', 'no', 'false', 'f', 'off']: return False msg = "Unable to interpret string value '%s' as boolean" % (value) diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index 9d99acf2..7585c92b 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -28,7 +28,9 @@ from charmhelpers.fetch import ( ) from charmhelpers.contrib.openstack.utils import ( + config_value_changed, configure_installation_source, + git_install_requested, openstack_upgrade_available, os_requires_version, ) @@ -43,6 +45,7 @@ from charmhelpers.payload.execd import execd_preinstall from nova_compute_utils import ( create_libvirt_secret, determine_packages, + git_install, import_authorized_keys, import_keystone_ca_cert, initialize_ssh_keys, @@ -84,9 +87,12 @@ CONFIGS = register_configs() def install(): execd_preinstall() configure_installation_source(config('openstack-origin')) + apt_update() apt_install(determine_packages(), fatal=True) + git_install(config('openstack-origin-git')) + @hooks.hook('config-changed') @restart_on_change(restart_map()) @@ -95,8 +101,12 @@ def config_changed(): assert_charm_supports_ipv6() global CONFIGS - if openstack_upgrade_available('nova-common'): - CONFIGS = do_openstack_upgrade() + if git_install_requested(): + if config_value_changed('openstack-origin-git'): + git_install(config('openstack-origin-git')) + else: + if openstack_upgrade_available('nova-common'): + CONFIGS = do_openstack_upgrade() sysctl_dict = config('sysctl') if sysctl_dict: diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index b3dbeb51..c6f75afb 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -1,4 +1,5 @@ import os +import shutil import pwd from base64 import b64decode @@ -8,16 +9,21 @@ from subprocess import check_call, check_output from charmhelpers.fetch import ( apt_update, apt_upgrade, - apt_install + apt_install, ) from charmhelpers.core.host import ( + adduser, + add_group, + add_user_to_group, mkdir, service_restart, - lsb_release + lsb_release, + write_file, ) from charmhelpers.core.hookenv import ( + charm_dir, config, log, related_units, @@ -27,6 +33,7 @@ from charmhelpers.core.hookenv import ( INFO, ) +from charmhelpers.core.templating import render from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute from charmhelpers.contrib.openstack import templating, context from charmhelpers.contrib.openstack.alternatives import install_alternative @@ -34,6 +41,9 @@ from charmhelpers.contrib.openstack.alternatives import install_alternative from charmhelpers.contrib.openstack.utils import ( configure_installation_source, get_os_codename_install_source, + git_install_requested, + git_clone_and_install, + git_src_dir, os_release ) @@ -57,9 +67,61 @@ TEMPLATES = 'templates/' BASE_PACKAGES = [ 'nova-compute', 'genisoimage', # was missing as a package dependency until raring. + 'librbd1', # bug 1440953 'python-six', ] +BASE_GIT_PACKAGES = [ + 'libvirt-bin', + 'libxml2-dev', + 'libxslt1-dev', + 'python-dev', + 'python-pip', + 'python-setuptools', + 'zlib1g-dev', +] + +LATE_GIT_PACKAGES = [ + 'bridge-utils', + 'dnsmasq-base', + 'dnsmasq-utils', + 'ebtables', + 'genisoimage', + 'iptables', + 'iputils-arping', + 'kpartx', + 'kvm', + 'netcat', + 'open-iscsi', + 'parted', + 'python-libvirt', + 'qemu', + 'qemu-system', + 'qemu-utils', + 'vlan', + 'xen-system-amd64', +] + +# ubuntu packages that should not be installed when deploying from git +GIT_PACKAGE_BLACKLIST = [ + 'neutron-plugin-openvswitch', + 'neutron-plugin-openvswitch-agent', + 'neutron-server', + 'nova-api', + 'nova-api-metadata', + 'nova-compute', + 'nova-compute-kvm', + 'nova-compute-lxc', + 'nova-compute-qemu', + 'nova-compute-uml', + 'nova-compute-xen', + 'nova-network', + 'python-six', + 'quantum-plugin-openvswitch', + 'quantum-plugin-openvswitch-agent', + 'quantum-server', +] + NOVA_CONF_DIR = "/etc/nova" QEMU_CONF = '/etc/libvirt/qemu.conf' LIBVIRTD_CONF = '/etc/libvirt/libvirtd.conf' @@ -104,7 +166,8 @@ BASE_RESOURCE_MAP = { context.ZeroMQContext(), context.NotificationDriverContext(), MetadataServiceContext(), - HostIPContext()], + HostIPContext(), + context.LogLevelContext()], }, } @@ -287,6 +350,14 @@ def determine_packages(): if enable_nova_metadata(): packages.append('nova-api-metadata') + if git_install_requested(): + packages = list(set(packages)) + packages.extend(BASE_GIT_PACKAGES) + # don't include packages that will be installed from git + for p in GIT_PACKAGE_BLACKLIST: + if p in packages: + packages.remove(p) + return packages @@ -525,3 +596,123 @@ def neutron_plugin_legacy_mode(): def manage_ovs(): return neutron_plugin_legacy_mode() and neutron_plugin() == 'ovs' + + +def git_install(projects_yaml): + """Perform setup, and install git repos specified in yaml parameter.""" + if git_install_requested(): + git_pre_install() + git_clone_and_install(projects_yaml, core_project='nova') + git_post_install(projects_yaml) + + +def git_pre_install(): + """Perform pre-install setup.""" + dirs = [ + '/var/lib/nova', + '/var/lib/nova/buckets', + '/var/lib/nova/CA', + '/var/lib/nova/CA/INTER', + '/var/lib/nova/CA/newcerts', + '/var/lib/nova/CA/private', + '/var/lib/nova/CA/reqs', + '/var/lib/nova/images', + '/var/lib/nova/instances', + '/var/lib/nova/keys', + '/var/lib/nova/networks', + '/var/lib/nova/tmp', + '/var/log/nova', + ] + + logs = [ + '/var/log/nova/nova-api.log', + '/var/log/nova/nova-compute.log', + '/var/log/nova/nova-manage.log', + '/var/log/nova/nova-network.log', + ] + + adduser('nova', shell='/bin/bash', system_user=True) + check_call(['usermod', '--home', '/var/lib/nova', 'nova']) + add_group('nova', system_group=True) + add_user_to_group('nova', 'nova') + add_user_to_group('nova', 'libvirtd') + + for d in dirs: + mkdir(d, owner='nova', group='nova', perms=0755, force=False) + + for l in logs: + write_file(l, '', owner='nova', group='nova', perms=0644) + + +def git_post_install(projects_yaml): + """Perform post-install setup.""" + src_etc = os.path.join(git_src_dir(projects_yaml, 'nova'), 'etc/nova') + configs = [ + {'src': src_etc, + 'dest': '/etc/nova'}, + ] + + for c in configs: + if os.path.exists(c['dest']): + shutil.rmtree(c['dest']) + shutil.copytree(c['src'], c['dest']) + + virt_type = VIRT_TYPES[config('virt-type')][0] + nova_compute_conf = 'git/{}.conf'.format(virt_type) + render(nova_compute_conf, '/etc/nova/nova-compute.conf', {}, perms=0o644) + render('git/nova_sudoers', '/etc/sudoers.d/nova_sudoers', {}, perms=0o440) + + service_name = 'nova-compute' + nova_user = 'nova' + start_dir = '/var/lib/nova' + nova_conf = 'etc/nova/nova.conf' + nova_api_metadata_context = { + 'service_description': 'Nova Metadata API server', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-metadata', + 'executable_name': '/usr/local/bin/nova-api-metadata', + 'config_files': [nova_conf], + } + nova_api_context = { + 'service_description': 'Nova API server', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api', + 'executable_name': '/usr/local/bin/nova-api', + 'config_files': [nova_conf], + } + nova_compute_context = { + 'service_description': 'Nova compute worker', + 'service_name': service_name, + 'user_name': nova_user, + 'process_name': 'nova-compute', + 'executable_name': '/usr/local/bin/nova-compute', + 'config_files': [nova_conf, '/etc/nova/nova-compute.conf'], + } + nova_network_context = { + 'service_description': 'Nova network worker', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-network', + 'executable_name': '/usr/local/bin/nova-network', + 'config_files': [nova_conf], + } + + # NOTE(coreycb): Needs systemd support + templates_dir = 'hooks/charmhelpers/contrib/openstack/templates' + templates_dir = os.path.join(charm_dir(), templates_dir) + render('git.upstart', '/etc/init/nova-api-metadata.conf', + nova_api_metadata_context, perms=0o644, templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-api.conf', + nova_api_context, perms=0o644, templates_dir=templates_dir) + render('git/upstart/nova-compute.upstart', '/etc/init/nova-compute.conf', + nova_compute_context, perms=0o644) + render('git.upstart', '/etc/init/nova-network.conf', + nova_network_context, perms=0o644, templates_dir=templates_dir) + + apt_update() + apt_install(LATE_GIT_PACKAGES, fatal=True) diff --git a/metadata.yaml b/metadata.yaml index 27962a38..8b7f916d 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -33,3 +33,6 @@ requires: zeromq-configuration: interface: zeromq-configuration scope: container +peers: + compute-peer: + interface: nova diff --git a/templates/git/nova-compute-kvm.conf b/templates/git/nova-compute-kvm.conf new file mode 100644 index 00000000..48ad4890 --- /dev/null +++ b/templates/git/nova-compute-kvm.conf @@ -0,0 +1,4 @@ +[DEFAULT] +compute_driver=libvirt.LibvirtDriver +[libvirt] +virt_type=kvm diff --git a/templates/git/nova-compute-lxc.conf b/templates/git/nova-compute-lxc.conf new file mode 100644 index 00000000..725f9f65 --- /dev/null +++ b/templates/git/nova-compute-lxc.conf @@ -0,0 +1,4 @@ +[DEFAULT] +compute_driver=libvirt.LibvirtDriver +[libvirt] +virt_type=lxc diff --git a/templates/git/nova-compute-qemu.conf b/templates/git/nova-compute-qemu.conf new file mode 100644 index 00000000..1ef5590c --- /dev/null +++ b/templates/git/nova-compute-qemu.conf @@ -0,0 +1,4 @@ +[DEFAULT] +compute_driver=libvirt.LibvirtDriver +[libvirt] +virt_type=qemu diff --git a/templates/git/nova-compute-vmware.conf b/templates/git/nova-compute-vmware.conf new file mode 100644 index 00000000..751b1239 --- /dev/null +++ b/templates/git/nova-compute-vmware.conf @@ -0,0 +1,2 @@ +[DEFAULT] +compute_driver=vmwareapi.VMwareVCDriver diff --git a/templates/git/nova-compute-xen.conf b/templates/git/nova-compute-xen.conf new file mode 100644 index 00000000..c76daad0 --- /dev/null +++ b/templates/git/nova-compute-xen.conf @@ -0,0 +1,4 @@ +[DEFAULT] +compute_driver=libvirt.LibvirtDriver +[libvirt] +virt_type=xen diff --git a/templates/git/nova_sudoers b/templates/git/nova_sudoers new file mode 100644 index 00000000..9bed09a0 --- /dev/null +++ b/templates/git/nova_sudoers @@ -0,0 +1,4 @@ +Defaults:nova !requiretty + +nova ALL = (root) NOPASSWD: /usr/local/bin/nova-rootwrap /etc/nova/rootwrap.conf * + diff --git a/templates/git/nova_tgt.conf b/templates/git/nova_tgt.conf new file mode 100644 index 00000000..d417feea --- /dev/null +++ b/templates/git/nova_tgt.conf @@ -0,0 +1 @@ +include /var/lib/nova/volumes/* diff --git a/templates/git/upstart/nova-compute.upstart b/templates/git/upstart/nova-compute.upstart new file mode 100755 index 00000000..b66c8263 --- /dev/null +++ b/templates/git/upstart/nova-compute.upstart @@ -0,0 +1,27 @@ +description "{{ service_description }}" +author "Juju {{ service_name }} Charm " + +start on runlevel [2345] +stop on runlevel [!2345] + +chdir /var/run + +pre-start script + mkdir -p /var/run/nova + chown nova:root /var/run/nova/ + + mkdir -p /var/lock/nova + chown nova:root /var/lock/nova/ + + modprobe nbd + + # If libvirt-bin is installed, always wait for it to start first + if status libvirt-bin; then + start wait-for-state WAIT_FOR=libvirt-bin WAIT_STATE=running WAITER={{ process_name }} + fi +end script + +exec start-stop-daemon --start --chuid {{ user_name }} --exec {{ executable_name }} -- \ + {% for config_file in config_files -%} + --config-file={{ config_file }} \ + {% endfor -%} diff --git a/templates/havana/nova.conf b/templates/havana/nova.conf index e88b92c5..2e0808aa 100644 --- a/templates/havana/nova.conf +++ b/templates/havana/nova.conf @@ -7,6 +7,8 @@ {% endif -%} ############################################################################### [DEFAULT] +verbose={{ verbose }} +debug={{ debug }} dhcpbridge_flagfile=/etc/nova/nova.conf dhcpbridge=/usr/bin/nova-dhcpbridge logdir=/var/log/nova @@ -21,7 +23,6 @@ libvirt_disk_prefix=vd libvirt_use_virtio_for_bridges=True {% endif -%} -verbose=True use_syslog = {{ use_syslog }} ec2_private_dns_show_ip=True api_paste_config=/etc/nova/api-paste.ini diff --git a/templates/icehouse/nova.conf b/templates/icehouse/nova.conf new file mode 100644 index 00000000..0cf8720f --- /dev/null +++ b/templates/icehouse/nova.conf @@ -0,0 +1,155 @@ +# icehouse +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +{% if restart_trigger -%} +# restart trigger: {{ restart_trigger }} +{% endif -%} +############################################################################### +[DEFAULT] +verbose={{ verbose }} +debug={{ debug }} +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 + +{% if arch == 'aarch64' -%} +libvirt_use_virtio_for_bridges=False +libvirt_disk_prefix=vd +{% else -%} +libvirt_use_virtio_for_bridges=True +{% endif -%} + +use_syslog = {{ use_syslog }} +ec2_private_dns_show_ip=True +api_paste_config=/etc/nova/api-paste.ini +enabled_apis=ec2,osapi_compute,metadata +auth_strategy=keystone +compute_driver=libvirt.LibvirtDriver +my_ip = {{ host_ip }} + +{% include "parts/database" %} + +{% include "parts/rabbitmq" %} + +{% if glance_api_servers -%} +glance_api_servers = {{ glance_api_servers }} +{% endif -%} + +{% if console_vnc_type -%} +vnc_enabled = True +novnc_enabled = True +vnc_keymap = {{ console_keymap }} +vncserver_listen = 0.0.0.0 +vncserver_proxyclient_address = {{ console_listen_addr }} +{% if console_access_protocol == 'novnc' or console_access_protocol == 'vnc' -%} +novncproxy_base_url = {{ novnc_proxy_address }} +{% endif -%} +{% if console_access_protocol == 'xvpvnc' or console_access_protocol == 'vnc' -%} +xvpvncproxy_port = {{ xvpvnc_proxy_port }} +xvpvncproxy_host = {{ xvpvnc_proxy_host }} +xvpvncproxy_base_url = {{ xvpvnc_proxy_address }} +{% endif -%} +{% else -%} +vnc_enabled = False +novnc_enabled = False +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'ovs' -%} +libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver +{% if neutron_security_groups -%} +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +network_api_class=nova.network.neutronv2.api.API +libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtGenericVIFDriver +neutron_ovs_bridge=alubr0 +security_group_api=nova +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if neutron_plugin and (neutron_plugin == 'nvp' or neutron_plugin == 'nsx') -%} +libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'Calico' -%} +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if network_manager_config -%} +{% for key, value in network_manager_config.iteritems() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if network_manager == 'neutron' -%} +network_api_class = nova.network.neutronv2.api.API +{% else -%} +network_manager = nova.network.manager.FlatDHCPManager +{% endif -%} + +{% if network_device_mtu -%} +network_device_mtu = {{ network_device_mtu }} +{% 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 live_migration_uri -%} +live_migration_uri = {{ live_migration_uri }} +{% endif -%} + +{% if instances_path -%} +instances_path = {{ instances_path }} +{% endif -%} + +{% if sections and 'DEFAULT' in sections -%} +{% for key, value in sections['DEFAULT'] -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if console_access_protocol == 'spice' -%} +[spice] +agent_enabled = True +enabled = True +html5proxy_base_url = {{ spice_proxy_address }} +keymap = {{ console_keymap }} +server_listen = 0.0.0.0 +server_proxyclient_address = {{ console_listen_addr }} +{% endif -%} + +[libvirt] +{% if libvirt_images_type -%} +images_type = {{ libvirt_images_type }} +images_rbd_pool = {{ rbd_pool }} +images_rbd_ceph_conf = {{ libvirt_rbd_images_ceph_conf }} +inject_password=false +inject_key=false +inject_partition=-2 +{% endif -%} +{% if rbd_pool -%} +rbd_pool = {{ rbd_pool }} +rbd_user = {{ rbd_user }} +rbd_secret_uuid = {{ rbd_secret_uuid }} +{% endif -%} +{% if disk_cachemodes -%} +disk_cachemodes = {{ disk_cachemodes }} +{% endif -%} + diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf index 65fb19f9..56e056cd 100644 --- a/templates/juno/nova.conf +++ b/templates/juno/nova.conf @@ -7,6 +7,8 @@ {% endif -%} ############################################################################### [DEFAULT] +verbose={{ verbose }} +debug={{ debug }} dhcpbridge_flagfile=/etc/nova/nova.conf dhcpbridge=/usr/bin/nova-dhcpbridge logdir=/var/log/nova @@ -21,7 +23,6 @@ libvirt_disk_prefix=vd libvirt_use_virtio_for_bridges=True {% endif -%} -verbose=True use_syslog = {{ use_syslog }} ec2_private_dns_show_ip=True api_paste_config=/etc/nova/api-paste.ini @@ -70,6 +71,14 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver {% endif -%} {% endif -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +network_api_class=nova.network.neutronv2.api.API +libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtGenericVIFDriver +neutron_ovs_bridge=alubr0 +security_group_api=nova +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + {% if neutron_plugin and (neutron_plugin == 'nvp' or neutron_plugin == 'nsx') -%} libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver security_group_api = neutron diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf index 385106cc..fd756250 100644 --- a/templates/kilo/nova.conf +++ b/templates/kilo/nova.conf @@ -7,12 +7,13 @@ {% endif -%} ############################################################################### [DEFAULT] +verbose={{ verbose }} +debug={{ debug }} dhcpbridge_flagfile=/etc/nova/nova.conf dhcpbridge=/usr/bin/nova-dhcpbridge logdir=/var/log/nova state_path=/var/lib/nova force_dhcp_release=True -verbose=True use_syslog = {{ use_syslog }} ec2_private_dns_show_ip=True api_paste_config=/etc/nova/api-paste.ini @@ -58,6 +59,14 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver {% endif -%} {% endif -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +network_api_class=nova.network.neutronv2.api.API +libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtGenericVIFDriver +neutron_ovs_bridge=alubr0 +security_group_api=nova +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + {% if neutron_plugin and (neutron_plugin == 'nvp' or neutron_plugin == 'nsx') -%} libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver security_group_api = neutron diff --git a/tests/10-basic-precise-essex b/tests/10-basic-precise-essex deleted file mode 100755 index 9a0413f3..00000000 --- a/tests/10-basic-precise-essex +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova compute deployment on precise-essex.""" - -from basic_deployment import NovaBasicDeployment - -if __name__ == '__main__': - deployment = NovaBasicDeployment(series='precise') - deployment.run_tests() diff --git a/tests/11-basic-precise-folsom b/tests/11-basic-precise-folsom deleted file mode 100755 index 59f0e25e..00000000 --- a/tests/11-basic-precise-folsom +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova compute deployment on precise-folsom.""" - -import amulet -from basic_deployment import NovaBasicDeployment - -if __name__ == '__main__': - # NOTE(coreycb): Skipping failing test until resolved. 'nova-manage db sync' - # fails in shared-db-relation-changed (only fails on folsom) - message = "Skipping failing test until resolved" - amulet.raise_status(amulet.SKIP, msg=message) - - deployment = NovaBasicDeployment(series='precise', - openstack='cloud:precise-folsom', - source='cloud:precise-updates/folsom') - deployment.run_tests() diff --git a/tests/12-basic-precise-grizzly b/tests/12-basic-precise-grizzly deleted file mode 100755 index 82e4a0e1..00000000 --- a/tests/12-basic-precise-grizzly +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova compute deployment on precise-grizzly.""" - -from basic_deployment import NovaBasicDeployment - -if __name__ == '__main__': - deployment = NovaBasicDeployment(series='precise', - openstack='cloud:precise-grizzly', - source='cloud:precise-updates/grizzly') - deployment.run_tests() diff --git a/tests/13-basic-precise-havana b/tests/13-basic-precise-havana deleted file mode 100755 index ba4043c8..00000000 --- a/tests/13-basic-precise-havana +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova compute deployment on precise-havana.""" - -from basic_deployment import NovaBasicDeployment - -if __name__ == '__main__': - deployment = NovaBasicDeployment(series='precise', - openstack='cloud:precise-havana', - source='cloud:precise-updates/havana') - deployment.run_tests() diff --git a/tests/16-basic-trusty-icehouse-git b/tests/16-basic-trusty-icehouse-git new file mode 100755 index 00000000..078f19fe --- /dev/null +++ b/tests/16-basic-trusty-icehouse-git @@ -0,0 +1,9 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova compute git deployment on trusty-icehouse.""" + +from basic_deployment import NovaBasicDeployment + +if __name__ == '__main__': + deployment = NovaBasicDeployment(series='trusty', git=True) + deployment.run_tests() diff --git a/tests/17-basic-trusty-juno b/tests/17-basic-trusty-juno new file mode 100755 index 00000000..6db6d4db --- /dev/null +++ b/tests/17-basic-trusty-juno @@ -0,0 +1,11 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova compute deployment on trusty-juno.""" + +from basic_deployment import NovaBasicDeployment + +if __name__ == '__main__': + deployment = NovaBasicDeployment(series='trusty', + openstack='cloud:trusty-juno', + source='cloud:trusty-updates/juno') + deployment.run_tests() diff --git a/tests/18-basic-trusty-juno-git b/tests/18-basic-trusty-juno-git new file mode 100755 index 00000000..472abc15 --- /dev/null +++ b/tests/18-basic-trusty-juno-git @@ -0,0 +1,12 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova compute git deployment on trusty-juno.""" + +from basic_deployment import NovaBasicDeployment + +if __name__ == '__main__': + deployment = NovaBasicDeployment(series='trusty', + openstack='cloud:trusty-juno', + source='cloud:trusty-updates/juno', + git=True) + deployment.run_tests() diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index aeb2fafe..d1ab9424 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -1,7 +1,9 @@ #!/usr/bin/python import amulet +import os import time +import yaml from charmhelpers.contrib.openstack.amulet.deployment import ( OpenStackAmuletDeployment @@ -14,15 +16,17 @@ from charmhelpers.contrib.openstack.amulet.utils import ( ) # Use DEBUG to turn on debug logging -u = OpenStackAmuletUtils(ERROR) +u = OpenStackAmuletUtils(DEBUG) class NovaBasicDeployment(OpenStackAmuletDeployment): """Amulet tests on a basic nova compute deployment.""" - def __init__(self, series=None, openstack=None, source=None, stable=True): + def __init__(self, series=None, openstack=None, source=None, git=False, + stable=True): """Deploy the entire test environment.""" super(NovaBasicDeployment, self).__init__(series, openstack, source, stable) + self.git = git self._add_services() self._add_relations() self._configure_services() @@ -65,6 +69,24 @@ class NovaBasicDeployment(OpenStackAmuletDeployment): """Configure all of the services.""" nova_config = {'config-flags': 'auto_assign_floating_ip=False', 'enable-live-migration': 'False'} + if self.git: + branch = 'stable/' + self._get_openstack_release_string() + amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY') + openstack_origin_git = { + 'repositories': [ + {'name': 'requirements', + 'repository': 'git://git.openstack.org/openstack/requirements', + 'branch': branch}, + {'name': 'nova', + 'repository': 'git://git.openstack.org/openstack/nova', + 'branch': branch}, + ], + 'directory': '/mnt/openstack-git', + 'http_proxy': amulet_http_proxy, + 'https_proxy': amulet_http_proxy, + } + nova_config['openstack-origin-git'] = yaml.dump(openstack_origin_git) + keystone_config = {'admin-password': 'openstack', 'admin-token': 'ubuntutesting'} configs = {'nova-compute': nova_config, 'keystone': keystone_config} @@ -362,7 +384,7 @@ class NovaBasicDeployment(OpenStackAmuletDeployment): 'lock_path': '/var/lock/nova', 'force_dhcp_release': 'True', 'libvirt_use_virtio_for_bridges': 'True', - 'verbose': 'True', + 'verbose': 'False', 'use_syslog': 'False', 'ec2_private_dns_show_ip': 'True', 'api_paste_config': '/etc/nova/api-paste.ini', @@ -377,8 +399,7 @@ class NovaBasicDeployment(OpenStackAmuletDeployment): 'glance_api_servers': glance_relation['glance-api-server'], 'flat_interface': 'eth1', 'network_manager': 'nova.network.manager.FlatDHCPManager', - 'volume_api_class': 'nova.volume.cinder.API', - 'verbose': 'True'} + 'volume_api_class': 'nova.volume.cinder.API'} ret = u.validate_config_data(unit, conf, 'DEFAULT', expected) if ret: diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index afaed60c..43aa3614 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -1,3 +1,4 @@ import sys +sys.path.append('actions/') sys.path.append('hooks/') diff --git a/unit_tests/test_actions_git_reinstall.py b/unit_tests/test_actions_git_reinstall.py new file mode 100644 index 00000000..d245cf34 --- /dev/null +++ b/unit_tests/test_actions_git_reinstall.py @@ -0,0 +1,87 @@ +from mock import patch + +with patch('charmhelpers.core.hookenv.config') as config: + config.return_value = 'nova' + import nova_compute_utils as utils # noqa + +with patch('nova_compute_utils.restart_map'): + with patch('nova_compute_utils.register_configs'): + import git_reinstall + +from test_utils import ( + CharmTestCase +) + +TO_PATCH = [ + 'config', +] + + +openstack_origin_git = \ + """repositories: + - {name: requirements, + repository: 'git://git.openstack.org/openstack/requirements', + branch: stable/juno} + - {name: nova, + repository: 'git://git.openstack.org/openstack/nova', + branch: stable/juno}""" + + +class TestnovaAPIActions(CharmTestCase): + + def setUp(self): + super(TestnovaAPIActions, self).setUp(git_reinstall, TO_PATCH) + self.config.side_effect = self.test_config.get + + @patch.object(git_reinstall, 'action_set') + @patch.object(git_reinstall, 'action_fail') + @patch.object(git_reinstall, 'git_install') + def test_git_reinstall(self, git_install, action_fail, action_set): + self.test_config.set('openstack-origin-git', openstack_origin_git) + + git_reinstall.git_reinstall() + + git_install.assert_called_with(openstack_origin_git) + self.assertTrue(git_install.called) + self.assertFalse(action_set.called) + self.assertFalse(action_fail.called) + + @patch.object(git_reinstall, 'action_set') + @patch.object(git_reinstall, 'action_fail') + @patch.object(git_reinstall, 'git_install') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_git_reinstall_not_configured(self, _config, git_install, + action_fail, action_set): + _config.return_value = None + + git_reinstall.git_reinstall() + + msg = 'openstack-origin-git is not configured' + action_fail.assert_called_with(msg) + self.assertFalse(git_install.called) + self.assertFalse(action_set.called) + + @patch.object(git_reinstall, 'action_set') + @patch.object(git_reinstall, 'action_fail') + @patch.object(git_reinstall, 'git_install') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_git_reinstall_exception(self, _config, git_install, + action_fail, action_set): + _config.return_value = openstack_origin_git + e = OSError('something bad happened') + git_install.side_effect = e + traceback = ( + "Traceback (most recent call last):\n" + " File \"actions/git_reinstall.py\", line 37, in git_reinstall\n" + " git_install(config(\'openstack-origin-git\'))\n" + " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 964, in __call__\n" # noqa + " return _mock_self._mock_call(*args, **kwargs)\n" + " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 1019, in _mock_call\n" # noqa + " raise effect\n" + "OSError: something bad happened\n") + + git_reinstall.git_reinstall() + + msg = 'git-reinstall resulted in an unexpected error' + action_fail.assert_called_with(msg) + action_set.assert_called_with({'traceback': traceback}) diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py index 2d171789..6c6f9243 100644 --- a/unit_tests/test_nova_compute_hooks.py +++ b/unit_tests/test_nova_compute_hooks.py @@ -3,6 +3,7 @@ from mock import ( patch, MagicMock ) +import yaml from test_utils import CharmTestCase @@ -48,6 +49,8 @@ TO_PATCH = [ 'disable_shell', 'enable_shell', 'update_nrpe_config', + 'git_install', + 'git_install_requested', # misc_utils 'ensure_ceph_keyring', 'execd_preinstall', @@ -77,13 +80,40 @@ class NovaComputeRelationsTests(CharmTestCase): self.apt_install.assert_called_with(['foo', 'bar'], fatal=True) self.execd_preinstall.assert_called() + def test_install_hook_git(self): + self.git_install_requested.return_value = True + self.determine_packages.return_value = ['foo', 'bar'] + repo = 'cloud:trusty-juno' + openstack_origin_git = { + 'repositories': [ + {'name': 'requirements', + 'repository': 'git://git.openstack.org/openstack/requirements', # noqa + 'branch': 'stable/juno'}, + {'name': 'nova', + 'repository': 'git://git.openstack.org/openstack/nova', + 'branch': 'stable/juno'} + ], + 'directory': '/mnt/openstack-git', + } + projects_yaml = yaml.dump(openstack_origin_git) + self.test_config.set('openstack-origin', repo) + self.test_config.set('openstack-origin-git', projects_yaml) + hooks.install() + 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.git_install.assert_called_with(projects_yaml) + self.execd_preinstall.assert_called() + def test_config_changed_with_upgrade(self): + self.git_install_requested.return_value = False self.openstack_upgrade_available.return_value = True hooks.config_changed() self.assertTrue(self.do_openstack_upgrade.called) @patch.object(hooks, 'compute_joined') def test_config_changed_with_migration(self, compute_joined): + self.git_install_requested.return_value = False self.migration_enabled.return_value = True _zmq_joined = self.patch('zeromq_configuration_relation_joined') self.test_config.set('migration-auth-type', 'ssh') @@ -102,6 +132,7 @@ class NovaComputeRelationsTests(CharmTestCase): @patch.object(hooks, 'compute_joined') def test_config_changed_with_resize(self, compute_joined): + self.git_install_requested.return_value = False self.test_config.set('enable-resize', True) _zmq_joined = self.patch('zeromq_configuration_relation_joined') self.relation_ids.return_value = [ @@ -120,6 +151,7 @@ class NovaComputeRelationsTests(CharmTestCase): @patch.object(hooks, 'compute_joined') def test_config_changed_without_resize(self, compute_joined): + self.git_install_requested.return_value = False self.test_config.set('enable-resize', False) _zmq_joined = self.patch('zeromq_configuration_relation_joined') self.relation_ids.return_value = [ @@ -137,6 +169,7 @@ class NovaComputeRelationsTests(CharmTestCase): @patch.object(hooks, 'compute_joined') def test_config_changed_no_upgrade_no_migration(self, compute_joined): + self.git_install_requested.return_value = False self.openstack_upgrade_available.return_value = False self.migration_enabled.return_value = False hooks.config_changed() @@ -145,10 +178,34 @@ class NovaComputeRelationsTests(CharmTestCase): @patch.object(hooks, 'compute_joined') def test_config_changed_with_sysctl(self, compute_joined): + self.git_install_requested.return_value = False self.test_config.set('sysctl', '{ kernel.max_pid : "1337" }') hooks.config_changed() self.create_sysctl.assert_called() + @patch.object(hooks, 'config_value_changed') + def test_config_changed_git(self, config_val_changed): + self.git_install_requested.return_value = True + repo = 'cloud:trusty-juno' + openstack_origin_git = { + 'repositories': [ + {'name': 'requirements', + 'repository': + 'git://git.openstack.org/openstack/requirements', + 'branch': 'stable/juno'}, + {'name': 'nova', + 'repository': 'git://git.openstack.org/openstack/nova', + 'branch': 'stable/juno'} + ], + 'directory': '/mnt/openstack-git', + } + projects_yaml = yaml.dump(openstack_origin_git) + self.test_config.set('openstack-origin', repo) + self.test_config.set('openstack-origin-git', projects_yaml) + hooks.config_changed() + self.git_install.assert_called_with(projects_yaml) + self.assertFalse(self.do_openstack_upgrade.called) + def test_amqp_joined(self): hooks.amqp_joined() self.relation_set.assert_called_with( diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index 6e9c180b..58af340d 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -35,6 +35,15 @@ OVS_PKGS = [ OVS_PKGS_FLAT = list(itertools.chain.from_iterable(OVS_PKGS)) +openstack_origin_git = \ + """repositories: + - {name: requirements, + repository: 'git://git.openstack.org/openstack/requirements', + branch: stable/juno} + - {name: nova, + repository: 'git://git.openstack.org/openstack/nova', + branch: stable/juno}""" + class NovaComputeUtilsTests(CharmTestCase): @@ -44,7 +53,10 @@ class NovaComputeUtilsTests(CharmTestCase): @patch.object(utils, 'enable_nova_metadata') @patch.object(utils, 'network_manager') - def test_determine_packages_nova_network(self, net_man, en_meta): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_nova_network(self, git_requested, net_man, + en_meta): + git_requested.return_value = False en_meta.return_value = False net_man.return_value = 'flatdhcpmanager' self.relation_ids.return_value = [] @@ -59,7 +71,10 @@ class NovaComputeUtilsTests(CharmTestCase): @patch.object(utils, 'enable_nova_metadata') @patch.object(utils, 'neutron_plugin') @patch.object(utils, 'network_manager') - def test_determine_packages_quantum(self, net_man, n_plugin, en_meta): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_quantum(self, git_requested, net_man, n_plugin, + en_meta): + git_requested.return_value = False en_meta.return_value = False self.neutron_plugin_attribute.return_value = OVS_PKGS net_man.return_value = 'quantum' @@ -73,8 +88,11 @@ class NovaComputeUtilsTests(CharmTestCase): @patch.object(utils, 'enable_nova_metadata') @patch.object(utils, 'neutron_plugin') @patch.object(utils, 'network_manager') - def test_determine_packages_quantum_legacy_off(self, net_man, n_plugin, + @patch.object(utils, 'git_install_requested') + def test_determine_packages_quantum_legacy_off(self, git_requested, + net_man, n_plugin, en_meta, leg_mode): + git_requested.return_value = False en_meta.return_value = False leg_mode.return_value = False self.neutron_plugin_attribute.return_value = OVS_PKGS @@ -89,8 +107,10 @@ class NovaComputeUtilsTests(CharmTestCase): @patch.object(utils, 'enable_nova_metadata') @patch.object(utils, 'neutron_plugin') @patch.object(utils, 'network_manager') - def test_determine_packages_quantum_ceph(self, net_man, n_plugin, en_meta, - leg_mode): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_quantum_ceph(self, git_requested, net_man, + n_plugin, en_meta, leg_mode): + git_requested.return_value = False en_meta.return_value = False leg_mode.return_value = True self.neutron_plugin_attribute.return_value = OVS_PKGS @@ -105,7 +125,10 @@ class NovaComputeUtilsTests(CharmTestCase): @patch.object(utils, 'enable_nova_metadata') @patch.object(utils, 'neutron_plugin') @patch.object(utils, 'network_manager') - def test_determine_packages_metadata(self, net_man, n_plugin, en_meta): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_metadata(self, git_requested, net_man, + n_plugin, en_meta): + git_requested.return_value = False en_meta.return_value = True self.neutron_plugin_attribute.return_value = OVS_PKGS net_man.return_value = 'bob' @@ -475,3 +498,163 @@ class NovaComputeUtilsTests(CharmTestCase): _neutron_plugin_legacy_mode.return_value = True _neutron_plugin.return_value = 'bobvs' self.assertFalse(utils.manage_ovs()) + + @patch.object(utils, 'git_install_requested') + @patch.object(utils, 'git_clone_and_install') + @patch.object(utils, 'git_post_install') + @patch.object(utils, 'git_pre_install') + def test_git_install(self, git_pre, git_post, git_clone_and_install, + git_requested): + projects_yaml = openstack_origin_git + git_requested.return_value = True + utils.git_install(projects_yaml) + self.assertTrue(git_pre.called) + git_clone_and_install.assert_called_with(openstack_origin_git, + core_project='nova') + self.assertTrue(git_post.called) + + @patch.object(utils, 'mkdir') + @patch.object(utils, 'write_file') + @patch.object(utils, 'add_user_to_group') + @patch.object(utils, 'add_group') + @patch.object(utils, 'adduser') + @patch.object(utils, 'check_call') + def test_git_pre_install(self, check_call, adduser, add_group, + add_user_to_group, write_file, mkdir): + utils.git_pre_install() + adduser.assert_called_with('nova', shell='/bin/bash', + system_user=True) + check_call.assert_called_with(['usermod', '--home', '/var/lib/nova', + 'nova']) + add_group.assert_called_with('nova', system_group=True) + expected = [ + call('nova', 'nova'), + call('nova', 'libvirtd'), + ] + self.assertEquals(add_user_to_group.call_args_list, expected) + expected = [ + call('/var/lib/nova', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/buckets', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/CA', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/CA/INTER', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/CA/newcerts', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/CA/private', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/CA/reqs', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/images', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/instances', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/keys', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/networks', owner='nova', + group='nova', perms=0755, force=False), + call('/var/lib/nova/tmp', owner='nova', + group='nova', perms=0755, force=False), + call('/var/log/nova', owner='nova', + group='nova', perms=0755, force=False), + ] + self.assertEquals(mkdir.call_args_list, expected) + expected = [ + call('/var/log/nova/nova-api.log', '', owner='nova', + group='nova', perms=0644), + call('/var/log/nova/nova-compute.log', '', owner='nova', + group='nova', perms=0644), + call('/var/log/nova/nova-manage.log', '', owner='nova', + group='nova', perms=0644), + call('/var/log/nova/nova-network.log', '', owner='nova', + group='nova', perms=0644), + ] + self.assertEquals(write_file.call_args_list, expected) + + @patch.object(utils, 'git_src_dir') + @patch.object(utils, 'service_restart') + @patch.object(utils, 'render') + @patch('os.path.join') + @patch('os.path.exists') + @patch('shutil.copytree') + @patch('shutil.rmtree') + @patch.object(utils, 'apt_install') + @patch.object(utils, 'apt_update') + def test_git_post_install(self, apt_update, apt_install, rmtree, copytree, + exists, join, render, service_restart, + git_src_dir): + projects_yaml = openstack_origin_git + join.return_value = 'joined-string' + utils.git_post_install(projects_yaml) + expected = [ + call('joined-string', '/etc/nova'), + ] + copytree.assert_has_calls(expected) + + service_name = 'nova-compute' + nova_user = 'nova' + start_dir = '/var/lib/nova' + nova_conf = 'etc/nova/nova.conf' + nova_api_metadata_context = { + 'service_description': 'Nova Metadata API server', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-metadata', + 'executable_name': '/usr/local/bin/nova-api-metadata', + 'config_files': [nova_conf], + } + nova_api_context = { + 'service_description': 'Nova API server', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api', + 'executable_name': '/usr/local/bin/nova-api', + 'config_files': [nova_conf], + } + nova_compute_context = { + 'service_description': 'Nova compute worker', + 'service_name': service_name, + 'user_name': nova_user, + 'process_name': 'nova-compute', + 'executable_name': '/usr/local/bin/nova-compute', + 'config_files': [nova_conf, '/etc/nova/nova-compute.conf'], + } + nova_network_context = { + 'service_description': 'Nova network worker', + 'service_name': service_name, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-network', + 'executable_name': '/usr/local/bin/nova-network', + 'config_files': [nova_conf], + } + expected = [ + call('git/nova-compute-kvm.conf', '/etc/nova/nova-compute.conf', + {}, perms=0o644), + call('git/nova_sudoers', '/etc/sudoers.d/nova_sudoers', + {}, perms=0o440), + call('git.upstart', '/etc/init/nova-api-metadata.conf', + nova_api_metadata_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-api.conf', + nova_api_context, perms=0o644, + templates_dir='joined-string'), + call('git/upstart/nova-compute.upstart', + '/etc/init/nova-compute.conf', + nova_compute_context, perms=0o644), + call('git.upstart', '/etc/init/nova-network.conf', + nova_network_context, perms=0o644, + templates_dir='joined-string'), + ] + self.assertEquals(render.call_args_list, expected) + self.assertTrue(apt_update.called) + apt_install.assert_called_with( + ['bridge-utils', 'dnsmasq-base', + 'dnsmasq-utils', 'ebtables', 'genisoimage', 'iptables', + 'iputils-arping', 'kpartx', 'kvm', 'netcat', 'open-iscsi', + 'parted', 'python-libvirt', 'qemu', 'qemu-system', + 'qemu-utils', 'vlan', 'xen-system-amd64'], fatal=True)