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 8e692b5b..b561e368 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: @@ -18,8 +18,10 @@ test: # coreycb note: The -v should only be temporary until Amulet sends # 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 + @juju test -v -p AMULET_HTTP_PROXY --timeout 1200 \ + 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse \ + 16-basic-trusty-icehouse-git 17-basic-trusty-juno \ + 18-basic-trusty-juno-git sync: bin/charm_helpers_sync.py @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml diff --git a/README.txt b/README.txt index bd722a31..873b8ca5 100644 --- a/README.txt +++ b/README.txt @@ -23,3 +23,88 @@ juju deploy postgresql juju add-relation "nova-cloud-controller:pgsql-nova-db" "postgresql:db" juju add-relation "nova-cloud-controller:pgsql-neutron-db" "postgresql:db" + +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..e00e5f38 --- /dev/null +++ b/actions.yaml @@ -0,0 +1,2 @@ +git-reinstall: + description: Reinstall nova-cloud-controller 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..418291f9 --- /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_cc_utils import ( + git_install, +) + +from nova_cc_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 fca9719c..bc3a56dd 100644 --- a/config.yaml +++ b/config.yaml @@ -14,6 +14,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. rabbit-user: default: nova 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_cc_context.py b/hooks/nova_cc_context.py index 0e31839c..2c178447 100644 --- a/hooks/nova_cc_context.py +++ b/hooks/nova_cc_context.py @@ -1,3 +1,5 @@ +import os + from charmhelpers.core.hookenv import ( config, relation_ids, @@ -329,4 +331,22 @@ class InstanceConsoleContext(context.OSContextGenerator): servers = [] ctxt['memcached_servers'] = ','.join(servers) + + # Configure nova-novncproxy https if nova-api is using https. + if https(): + cn = resolve_address(endpoint_type=INTERNAL) + if cn: + cert_filename = 'cert_{}'.format(cn) + key_filename = 'key_{}'.format(cn) + else: + cert_filename = 'cert' + key_filename = 'key' + + ssl_dir = '/etc/apache2/ssl/nova' + cert = os.path.join(ssl_dir, cert_filename) + key = os.path.join(ssl_dir, key_filename) + if os.path.exists(cert) and os.path.exists(key): + ctxt['ssl_cert'] = cert + ctxt['ssl_key'] = key + return ctxt diff --git a/hooks/nova_cc_hooks.py b/hooks/nova_cc_hooks.py index 7a439176..52c6b939 100755 --- a/hooks/nova_cc_hooks.py +++ b/hooks/nova_cc_hooks.py @@ -43,7 +43,9 @@ from charmhelpers.fetch import ( ) from charmhelpers.contrib.openstack.utils import ( + config_value_changed, configure_installation_source, + git_install_requested, openstack_upgrade_available, os_release, os_requires_version, @@ -75,6 +77,7 @@ from nova_cc_utils import ( disable_services, do_openstack_upgrade, enable_services, + git_install, keystone_ca_cert_b64, migrate_neutron_database, migrate_nova_database, @@ -132,9 +135,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')) + _files = os.path.join(charm_dir(), 'files') if os.path.isdir(_files): for f in os.listdir(_files): @@ -160,16 +166,21 @@ def config_changed(): relation_prefix='nova') global CONFIGS - if openstack_upgrade_available('nova-common'): - CONFIGS = do_openstack_upgrade() - [neutron_api_relation_joined(rid=rid, remote_restart=True) - for rid in relation_ids('neutron-api')] + 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() + [neutron_api_relation_joined(rid=rid, remote_restart=True) + for rid in relation_ids('neutron-api')] save_script_rc() configure_https() CONFIGS.write_all() if console_attributes('protocol'): - apt_update() - apt_install(console_attributes('packages'), fatal=True) + if not git_install_requested(): + apt_update() + apt_install(console_attributes('packages'), fatal=True) [compute_joined(rid=rid) for rid in relation_ids('cloud-compute')] for r_id in relation_ids('identity-service'): diff --git a/hooks/nova_cc_utils.py b/hooks/nova_cc_utils.py index d46315c9..4814d8ad 100644 --- a/hooks/nova_cc_utils.py +++ b/hooks/nova_cc_utils.py @@ -1,4 +1,5 @@ import os +import shutil import subprocess import ConfigParser @@ -19,6 +20,9 @@ from charmhelpers.contrib.openstack.utils import ( get_host_ip, get_hostname, get_os_codename_install_source, + git_install_requested, + git_clone_and_install, + git_src_dir, is_ip, os_release, save_script_rc as _save_script_rc) @@ -31,6 +35,7 @@ from charmhelpers.fetch import ( ) from charmhelpers.core.hookenv import ( + charm_dir, config, log, relation_get, @@ -42,13 +47,19 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.core.host import ( + adduser, + add_group, + add_user_to_group, + mkdir, service, service_start, service_stop, service_running, - lsb_release + lsb_release, ) +from charmhelpers.core.templating import render + from charmhelpers.contrib.network.ip import ( is_ipv6 ) @@ -72,6 +83,40 @@ BASE_PACKAGES = [ 'python-memcache', ] +BASE_GIT_PACKAGES = [ + 'libxml2-dev', + 'libxslt1-dev', + 'python-dev', + 'python-pip', + 'python-setuptools', + 'zlib1g-dev', +] + +LATE_GIT_PACKAGES = [ + 'novnc', + 'spice-html5', + 'websockify', +] + +# ubuntu packages that should not be installed when deploying from git +GIT_PACKAGE_BLACKLIST = [ + 'neutron-server', + 'nova-api-ec2', + 'nova-api-os-compute', + 'nova-api-os-volume', + 'nova-cert', + 'nova-conductor', + 'nova-consoleauth', + 'nova-novncproxy', + 'nova-objectstore', + 'nova-scheduler', + 'nova-spiceproxy', + 'nova-xvpvncproxy', + 'python-keystoneclient', + 'python-six', + 'quantum-server', +] + BASE_SERVICES = [ 'nova-api-ec2', 'nova-api-os-compute', @@ -377,6 +422,15 @@ def determine_packages(): packages.extend(pkgs) if console_attributes('packages'): packages.extend(console_attributes('packages')) + + 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 list(set(packages)) @@ -974,3 +1028,210 @@ def setup_ipv6(): ' main') apt_update() apt_install('haproxy/trusty-backports', fatal=True) + + +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', + ] + + adduser('nova', shell='/bin/bash', system_user=True) + subprocess.check_call(['usermod', '--home', '/var/lib/nova', 'nova']) + add_group('nova', system_group=True) + add_user_to_group('nova', 'nova') + + for d in dirs: + mkdir(d, owner='nova', group='nova', perms=0755, force=False) + + +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']) + + render('git/nova_sudoers', '/etc/sudoers.d/nova_sudoers', {}, perms=0o440) + + nova_cc = 'nova-cloud-controller' + nova_user = 'nova' + start_dir = '/var/lib/nova' + nova_conf = '/etc/nova/nova.conf' + nova_ec2_api_context = { + 'service_description': 'Nova EC2 API server', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-ec2', + 'executable_name': '/usr/local/bin/nova-api-ec2', + 'config_files': [nova_conf], + } + nova_api_os_compute_context = { + 'service_description': 'Nova OpenStack Compute API server', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-os-compute', + 'executable_name': '/usr/local/bin/nova-api-os-compute', + 'config_files': [nova_conf], + } + nova_cells_context = { + 'service_description': 'Nova cells', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-cells', + 'executable_name': '/usr/local/bin/nova-cells', + 'config_files': [nova_conf], + } + nova_cert_context = { + 'service_description': 'Nova cert', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-cert', + 'executable_name': '/usr/local/bin/nova-cert', + 'config_files': [nova_conf], + } + nova_conductor_context = { + 'service_description': 'Nova conductor', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-conductor', + 'executable_name': '/usr/local/bin/nova-conductor', + 'config_files': [nova_conf], + } + nova_consoleauth_context = { + 'service_description': 'Nova console auth', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-consoleauth', + 'executable_name': '/usr/local/bin/nova-consoleauth', + 'config_files': [nova_conf], + } + nova_console_context = { + 'service_description': 'Nova console', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-console', + 'executable_name': '/usr/local/bin/nova-console', + 'config_files': [nova_conf], + } + nova_novncproxy_context = { + 'service_description': 'Nova NoVNC proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-novncproxy', + 'executable_name': '/usr/local/bin/nova-novncproxy', + 'config_files': [nova_conf], + } + nova_objectstore_context = { + 'service_description': 'Nova object store', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-objectstore', + 'executable_name': '/usr/local/bin/nova-objectstore', + 'config_files': [nova_conf], + } + nova_scheduler_context = { + 'service_description': 'Nova scheduler', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-scheduler', + 'executable_name': '/usr/local/bin/nova-scheduler', + 'config_files': [nova_conf], + } + nova_spiceproxy_context = { + 'service_description': 'Nova spice proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-spicehtml5proxy', + 'executable_name': '/usr/local/bin/nova-spicehtml5proxy', + 'config_files': [nova_conf], + } + nova_xvpvncproxy_context = { + 'service_description': 'Nova XVPVNC proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-xvpvncproxy', + 'executable_name': '/usr/local/bin/nova-xvpvncproxy', + '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-ec2.conf', + nova_ec2_api_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-api-os-compute.conf', + nova_api_os_compute_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-cells.conf', + nova_cells_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-cert.conf', + nova_cert_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-conductor.conf', + nova_conductor_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-consoleauth.conf', + nova_consoleauth_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-console.conf', + nova_console_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-novncproxy.conf', + nova_novncproxy_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-objectstore.conf', + nova_objectstore_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-scheduler.conf', + nova_scheduler_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-spiceproxy.conf', + nova_spiceproxy_context, perms=0o644, + templates_dir=templates_dir) + render('git.upstart', '/etc/init/nova-xvpvncproxy.conf', + nova_xvpvncproxy_context, perms=0o644, + templates_dir=templates_dir) + + apt_update() + apt_install(LATE_GIT_PACKAGES, fatal=True) 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/icehouse/nova.conf b/templates/icehouse/nova.conf index 9a43285d..fdabd4bf 100644 --- a/templates/icehouse/nova.conf +++ b/templates/icehouse/nova.conf @@ -42,6 +42,8 @@ my_ip = {{ host_ip }} memcached_servers = {{ memcached_servers }} {% endif %} +{% include "parts/novnc" %} + {% if keystone_ec2_url -%} keystone_ec2_url = {{ keystone_ec2_url }} {% endif -%} @@ -70,6 +72,10 @@ default_floating_pool = {{ external_network }} {% endif -%} {% endif -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +neutron_ovs_bridge = alubr0 +{% endif -%} + {% if neutron_plugin and neutron_plugin == 'nvp' -%} security_group_api = neutron nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf index 274d5360..70f87fbd 100644 --- a/templates/juno/nova.conf +++ b/templates/juno/nova.conf @@ -42,6 +42,8 @@ my_ip = {{ host_ip }} memcached_servers = {{ memcached_servers }} {% endif %} +{% include "parts/novnc" %} + {% if keystone_ec2_url -%} keystone_ec2_url = {{ keystone_ec2_url }} {% endif -%} @@ -70,6 +72,10 @@ default_floating_pool = {{ external_network }} {% endif -%} {% endif -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +neutron_ovs_bridge = alubr0 +{% endif -%} + {% if neutron_plugin and neutron_plugin == 'nvp' -%} security_group_api = neutron nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf index efa9e1ff..61485263 100644 --- a/templates/kilo/nova.conf +++ b/templates/kilo/nova.conf @@ -41,6 +41,8 @@ my_ip = {{ host_ip }} memcached_servers = {{ memcached_servers }} {% endif %} +{% include "parts/novnc" %} + {% if keystone_ec2_url -%} keystone_ec2_url = {{ keystone_ec2_url }} {% endif -%} @@ -63,6 +65,10 @@ default_floating_pool = {{ external_network }} {% endif -%} {% endif -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +neutron_ovs_bridge = alubr0 +{% endif -%} + {% if neutron_plugin and neutron_plugin == 'nvp' -%} security_group_api = neutron nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver diff --git a/templates/parts/novnc b/templates/parts/novnc new file mode 100644 index 00000000..fc3d6336 --- /dev/null +++ b/templates/parts/novnc @@ -0,0 +1,9 @@ +{%- if ssl_only -%} +ssl_only=true +{% endif -%} +{% if ssl_cert -%} +cert={{ ssl_cert }} +{% endif -%} +{% if ssl_key -%} +key={{ ssl_key }} +{% endif %} diff --git a/tests/10-basic-precise-essex b/tests/10-basic-precise-essex deleted file mode 100755 index d5fd239a..00000000 --- a/tests/10-basic-precise-essex +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova cloud controller deployment on - precise-essex.""" - -from basic_deployment import NovaCCBasicDeployment - -if __name__ == '__main__': - deployment = NovaCCBasicDeployment(series='precise') - deployment.run_tests() diff --git a/tests/11-basic-precise-folsom b/tests/11-basic-precise-folsom deleted file mode 100755 index 4be178c3..00000000 --- a/tests/11-basic-precise-folsom +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova cloud controller deployment on - precise-folsom.""" - -import amulet -from basic_deployment import NovaCCBasicDeployment - -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 = NovaCCBasicDeployment(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 29fe56c5..00000000 --- a/tests/12-basic-precise-grizzly +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova cloud controller deployment on - precise-grizzly.""" - -from basic_deployment import NovaCCBasicDeployment - -if __name__ == '__main__': - deployment = NovaCCBasicDeployment(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 0841c113..00000000 --- a/tests/13-basic-precise-havana +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python - -"""Amulet tests on a basic nova cloud controller deployment on - precise-havana.""" - -from basic_deployment import NovaCCBasicDeployment - -if __name__ == '__main__': - deployment = NovaCCBasicDeployment(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..980922f2 --- /dev/null +++ b/tests/16-basic-trusty-icehouse-git @@ -0,0 +1,10 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova cloud controller git deployment on + trusty-icehouse.""" + +from basic_deployment import NovaCCBasicDeployment + +if __name__ == '__main__': + deployment = NovaCCBasicDeployment(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..639f1c48 --- /dev/null +++ b/tests/17-basic-trusty-juno @@ -0,0 +1,12 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova cloud controller deployment on + trusty-juno.""" + +from basic_deployment import NovaCCBasicDeployment + +if __name__ == '__main__': + deployment = NovaCCBasicDeployment(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..c5fd7f29 --- /dev/null +++ b/tests/18-basic-trusty-juno-git @@ -0,0 +1,13 @@ +#!/usr/bin/python + +"""Amulet tests on a basic nova cloud controller git deployment on + trusty-juno.""" + +from basic_deployment import NovaCCBasicDeployment + +if __name__ == '__main__': + deployment = NovaCCBasicDeployment(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 cfe8a419..57c00610 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -1,6 +1,8 @@ #!/usr/bin/python import amulet +import os +import yaml from charmhelpers.contrib.openstack.amulet.deployment import ( OpenStackAmuletDeployment @@ -13,15 +15,17 @@ from charmhelpers.contrib.openstack.amulet.utils import ( ) # Use DEBUG to turn on debug logging -u = OpenStackAmuletUtils(ERROR) +u = OpenStackAmuletUtils(DEBUG) class NovaCCBasicDeployment(OpenStackAmuletDeployment): """Amulet tests on a basic nova cloud controller deployment.""" - def __init__(self, series=None, openstack=None, source=None, stable=True): + def __init__(self, series=None, openstack=None, source=None, git=False, + stable=False): """Deploy the entire test environment.""" super(NovaCCBasicDeployment, self).__init__(series, openstack, source, stable) + self.git = git self._add_services() self._add_relations() self._configure_services() @@ -62,9 +66,28 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): def _configure_services(self): """Configure all of the services.""" + nova_cc_config = {} + 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_cc_config['openstack-origin-git'] = yaml.dump(openstack_origin_git) keystone_config = {'admin-password': 'openstack', 'admin-token': 'ubuntutesting'} - configs = {'keystone': keystone_config} + configs = {'nova-cloud-controller': nova_cc_config, + 'keystone': keystone_config} super(NovaCCBasicDeployment, self)._configure_services(configs) def _initialize_tests(self): diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 415b2110..43aa3614 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -1,2 +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..9ba0e6b6 --- /dev/null +++ b/unit_tests/test_actions_git_reinstall.py @@ -0,0 +1,98 @@ +from mock import patch, MagicMock + +with patch('charmhelpers.core.hookenv.config') as config: + config.return_value = 'nova' + import nova_cc_utils as utils # noqa + +_reg = utils.register_configs +_map = utils.restart_map + +utils.register_configs = MagicMock() +utils.restart_map = MagicMock() + +with patch('nova_cc_utils.guard_map') as gmap: + with patch('charmhelpers.core.hookenv.config') as config: + config.return_value = False + gmap.return_value = {} + import git_reinstall + +utils.register_configs = _reg +utils.restart_map = _map + +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_cc_contexts.py b/unit_tests/test_nova_cc_contexts.py index fdd63334..5a69b8d9 100644 --- a/unit_tests/test_nova_cc_contexts.py +++ b/unit_tests/test_nova_cc_contexts.py @@ -9,9 +9,7 @@ import mock from charmhelpers.core import hookenv _conf = hookenv.config hookenv.config = mock.MagicMock() -import nova_cc_utils as _utils -# this assert is a double check + to avoid pep8 warning -assert _utils.config == hookenv.config +import nova_cc_utils as _utils # noqa hookenv.config = _conf ##### @@ -47,6 +45,8 @@ class NovaComputeContextTests(CharmTestCase): self.config.side_effect = self.test_config.get self.log.side_effect = fake_log + @mock.patch.object(context, 'resolve_address', + lambda *args, **kwargs: None) @mock.patch.object(utils, 'os_release') @mock.patch('charmhelpers.contrib.network.ip.log') def test_instance_console_context_without_memcache(self, os_release, log_): @@ -57,6 +57,8 @@ class NovaComputeContextTests(CharmTestCase): self.assertEqual({'memcached_servers': ''}, instance_console()) + @mock.patch.object(context, 'resolve_address', + lambda *args, **kwargs: None) @mock.patch.object(utils, 'os_release') @mock.patch('charmhelpers.contrib.network.ip.log') def test_instance_console_context_with_memcache(self, os_release, log_): @@ -64,6 +66,8 @@ class NovaComputeContextTests(CharmTestCase): '127.0.1.1', '127.0.1.1') + @mock.patch.object(context, 'resolve_address', + lambda *args, **kwargs: None) @mock.patch.object(utils, 'os_release') @mock.patch('charmhelpers.contrib.network.ip.log') def test_instance_console_context_with_memcache_ipv6(self, os_release, diff --git a/unit_tests/test_nova_cc_hooks.py b/unit_tests/test_nova_cc_hooks.py index 005ed0e5..b254ef21 100644 --- a/unit_tests/test_nova_cc_hooks.py +++ b/unit_tests/test_nova_cc_hooks.py @@ -1,6 +1,7 @@ from mock import MagicMock, patch, call from test_utils import CharmTestCase, patch_open import os +import yaml with patch('charmhelpers.core.hookenv.config') as config: config.return_value = 'neutron' @@ -69,6 +70,8 @@ TO_PATCH = [ 'get_iface_for_address', 'get_netmask_for_address', 'update_nrpe_config', + 'git_install', + 'git_install_requested', ] @@ -106,18 +109,70 @@ class NovaCCHooksTests(CharmTestCase): self.disable_services.assert_called() self.cmd_all_services.assert_called_with('stop') + def test_install_hook_git(self): + self.git_install_requested.return_value = True + self.determine_packages.return_value = ['foo', 'bar'] + self.determine_ports.return_value = [80, 81, 82] + 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.git_install.assert_called_with(projects_yaml) + self.apt_install.assert_called_with(['foo', 'bar'], fatal=True) + self.execd_preinstall.assert_called() + self.disable_services.assert_called() + self.cmd_all_services.assert_called_with('stop') + @patch.object(hooks, 'configure_https') def test_config_changed_no_upgrade(self, conf_https): + self.git_install_requested.return_value = False self.openstack_upgrade_available.return_value = False hooks.config_changed() self.assertTrue(self.save_script_rc.called) + @patch.object(hooks, 'config_value_changed') + @patch.object(hooks, 'configure_https') + def test_config_changed_git(self, configure_https, 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) + @patch.object(hooks, 'cluster_joined') @patch.object(hooks, 'identity_joined') @patch.object(hooks, 'neutron_api_relation_joined') @patch.object(hooks, 'configure_https') def test_config_changed_with_upgrade(self, conf_https, neutron_api_joined, identity_joined, cluster_joined): + self.git_install_requested.return_value = False self.openstack_upgrade_available.return_value = True self.relation_ids.return_value = ['generic_rid'] _zmq_joined = self.patch('zeromq_configuration_relation_joined') diff --git a/unit_tests/test_nova_cc_utils.py b/unit_tests/test_nova_cc_utils.py index 9b0a332c..576c2569 100644 --- a/unit_tests/test_nova_cc_utils.py +++ b/unit_tests/test_nova_cc_utils.py @@ -127,6 +127,15 @@ DPKG_OPTS = [ '--option', 'Dpkg::Options::=--force-confdef', ] +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}""" + def fake_plugin_attribute(plugin, attr, net_manager): if plugin in PLUGIN_ATTRIBUTES: @@ -298,26 +307,34 @@ class NovaCCUtilsTests(CharmTestCase): self.assertEquals(_proxy_page, None) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_quantum(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_quantum(self, git_requested, subcontext): + git_requested.return_value = False self._resource_map(network_manager='quantum') pkgs = utils.determine_packages() self.assertIn('quantum-server', pkgs) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_neutron(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_neutron(self, git_requested, subcontext): + git_requested.return_value = False self.is_relation_made.return_value = False self._resource_map(network_manager='neutron') pkgs = utils.determine_packages() self.assertIn('neutron-server', pkgs) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_nova_volume(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_nova_volume(self, git_requested, subcontext): + git_requested.return_value = False self.relation_ids.return_value = ['nova-volume-service:0'] pkgs = utils.determine_packages() self.assertIn('nova-api-os-volume', pkgs) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_console(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_console(self, git_requested, subcontext): + git_requested.return_value = False self.test_config.set('console-access-protocol', 'spice') self.relation_ids.return_value = [] pkgs = utils.determine_packages() @@ -326,7 +343,9 @@ class NovaCCUtilsTests(CharmTestCase): self.assertIn(console_pkg, pkgs) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_base(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_base(self, git_requested, subcontext): + git_requested.return_value = False self.relation_ids.return_value = [] self.os_release.return_value = 'folsom' pkgs = utils.determine_packages() @@ -334,7 +353,10 @@ class NovaCCUtilsTests(CharmTestCase): self.assertEquals(ex, pkgs) @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') - def test_determine_packages_base_grizzly_beyond(self, subcontext): + @patch.object(utils, 'git_install_requested') + def test_determine_packages_base_grizzly_beyond(self, git_requested, + subcontext): + git_requested.return_value = False self.relation_ids.return_value = [] self.os_release.return_value = 'grizzly' pkgs = utils.determine_packages() @@ -824,3 +846,237 @@ class NovaCCUtilsTests(CharmTestCase): self.assertFalse(self.service_running.called) self.assertFalse(self.service_stop.called) self.assertTrue(contexts.complete_contexts.called) + + @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, 'add_user_to_group') + @patch.object(utils, 'add_group') + @patch.object(utils, 'adduser') + @patch('subprocess.check_call') + def test_git_pre_install(self, check_call, adduser, add_group, + add_user_to_group, 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'), + ] + 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) + + @patch.object(utils, 'git_src_dir') + @patch.object(utils, 'render') + @patch('os.path.join') + @patch('os.path.exists') + @patch('shutil.copytree') + @patch('shutil.rmtree') + def test_git_post_install(self, rmtree, copytree, exists, join, render, + 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) + + nova_cc = 'nova-cloud-controller' + nova_user = 'nova' + start_dir = '/var/lib/nova' + nova_conf = '/etc/nova/nova.conf' + nova_ec2_api_context = { + 'service_description': 'Nova EC2 API server', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-ec2', + 'executable_name': '/usr/local/bin/nova-api-ec2', + 'config_files': [nova_conf], + } + nova_api_os_compute_context = { + 'service_description': 'Nova OpenStack Compute API server', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-api-os-compute', + 'executable_name': '/usr/local/bin/nova-api-os-compute', + 'config_files': [nova_conf], + } + nova_cells_context = { + 'service_description': 'Nova cells', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-cells', + 'executable_name': '/usr/local/bin/nova-cells', + 'config_files': [nova_conf], + } + nova_cert_context = { + 'service_description': 'Nova cert', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-cert', + 'executable_name': '/usr/local/bin/nova-cert', + 'config_files': [nova_conf], + } + nova_conductor_context = { + 'service_description': 'Nova conductor', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-conductor', + 'executable_name': '/usr/local/bin/nova-conductor', + 'config_files': [nova_conf], + } + nova_consoleauth_context = { + 'service_description': 'Nova console auth', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-consoleauth', + 'executable_name': '/usr/local/bin/nova-consoleauth', + 'config_files': [nova_conf], + } + nova_console_context = { + 'service_description': 'Nova console', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-console', + 'executable_name': '/usr/local/bin/nova-console', + 'config_files': [nova_conf], + } + nova_novncproxy_context = { + 'service_description': 'Nova NoVNC proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-novncproxy', + 'executable_name': '/usr/local/bin/nova-novncproxy', + 'config_files': [nova_conf], + } + nova_objectstore_context = { + 'service_description': 'Nova object store', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-objectstore', + 'executable_name': '/usr/local/bin/nova-objectstore', + 'config_files': [nova_conf], + } + nova_scheduler_context = { + 'service_description': 'Nova scheduler', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-scheduler', + 'executable_name': '/usr/local/bin/nova-scheduler', + 'config_files': [nova_conf], + } + nova_spiceproxy_context = { + 'service_description': 'Nova spice proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-spicehtml5proxy', + 'executable_name': '/usr/local/bin/nova-spicehtml5proxy', + 'config_files': [nova_conf], + } + nova_xvpvncproxy_context = { + 'service_description': 'Nova XVPVNC proxy', + 'service_name': nova_cc, + 'user_name': nova_user, + 'start_dir': start_dir, + 'process_name': 'nova-xvpvncproxy', + 'executable_name': '/usr/local/bin/nova-xvpvncproxy', + 'config_files': [nova_conf], + } + expected = [ + call('git/nova_sudoers', '/etc/sudoers.d/nova_sudoers', + {}, perms=0o440), + call('git.upstart', '/etc/init/nova-api-ec2.conf', + nova_ec2_api_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-api-os-compute.conf', + nova_api_os_compute_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-cells.conf', + nova_cells_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-cert.conf', + nova_cert_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-conductor.conf', + nova_conductor_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-consoleauth.conf', + nova_consoleauth_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-console.conf', + nova_console_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-novncproxy.conf', + nova_novncproxy_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-objectstore.conf', + nova_objectstore_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-scheduler.conf', + nova_scheduler_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-spiceproxy.conf', + nova_spiceproxy_context, perms=0o644, + templates_dir='joined-string'), + call('git.upstart', '/etc/init/nova-xvpvncproxy.conf', + nova_xvpvncproxy_context, perms=0o644, + templates_dir='joined-string'), + ] + self.assertEquals(render.call_args_list, expected) + self.assertTrue(self.apt_update.called) + self.apt_install.assert_called_with(['novnc', 'spice-html5', + 'websockify'], fatal=True)