diff --git a/packstack/plugins/nova_300.py b/packstack/plugins/nova_300.py index 6c9c9d9da..77fb9f814 100644 --- a/packstack/plugins/nova_300.py +++ b/packstack/plugins/nova_300.py @@ -6,8 +6,9 @@ import os import uuid import logging import platform +import socket -from packstack.installer import processors, utils, validators +from packstack.installer import basedefs, processors, utils, validators from packstack.installer.exceptions import ScriptRuntimeError from packstack.modules.shortcuts import get_mq @@ -408,6 +409,10 @@ def initSequences(controller): {'title': 'Adding Nova Keystone manifest entries', 'functions':[createkeystonemanifest]}, {'title': 'Adding Nova Cert manifest entries', 'functions':[createcertmanifest]}, {'title': 'Adding Nova Conductor manifest entries', 'functions':[createconductormanifest]}, + {'title': 'Creating ssh keys for Nova migration', + 'functions':[create_ssh_keys]}, + {'title': 'Gathering ssh host keys for Nova migration', + 'functions':[gather_host_keys]}, {'title': 'Adding Nova Compute manifest entries', 'functions':[createcomputemanifest]}, {'title': 'Adding Nova Scheduler manifest entries', 'functions':[createschedmanifest]}, {'title': 'Adding Nova VNC Proxy manifest entries', 'functions':[createvncproxymanifest]}, @@ -496,8 +501,54 @@ def bring_up_ifcfg(host, device): raise ScriptRuntimeError(msg) +def create_ssh_keys(config): + migration_key = os.path.join(basedefs.VAR_DIR, 'nova_migration_key') + # Generate key + local = utils.ScriptRunner() + local.append('ssh-keygen -t rsa -b 2048 -f "%s" -N ""' % migration_key) + local.execute() + + with open(migration_key) as fp: + secret = fp.read().strip() + with open('%s.pub' % migration_key) as fp: + public = fp.read().strip() + + config['NOVA_MIGRATION_KEY_TYPE'] = 'ssh-rsa' + config['NOVA_MIGRATION_KEY_PUBLIC'] = public.split()[1] + config['NOVA_MIGRATION_KEY_SECRET'] = secret + +def gather_host_keys(config): + global compute_hosts + + for host in compute_hosts: + local = utils.ScriptRunner() + local.append('ssh-keyscan %s' % host) + retcode, hostkey = local.execute() + config['HOST_KEYS_%s' % host] = hostkey + def createcomputemanifest(config): global compute_hosts, network_hosts + + ssh_hostkeys = '' + for host in compute_hosts: + try: + host_name, host_aliases, host_addrs = socket.gethostbyaddr(host) + except socket.herror: + host_name, host_aliases, host_addrs = (host, [], []) + + for hostkey in config['HOST_KEYS_%s' %host].split('\n'): + hostkey = hostkey.strip() + if not hostkey: + continue + + _, host_key_type, host_key_data = hostkey.split() + config['SSH_HOST_NAME'] = host_name + config['SSH_HOST_ALIASES'] = ','.join('"%s"' % addr + for addr in host_aliases + host_addrs) + config['SSH_HOST_KEY'] = host_key_data + config['SSH_HOST_KEY_TYPE'] = host_key_type + ssh_hostkeys += getManifestTemplate("sshkey.pp") + for host in compute_hosts: config["CONFIG_NOVA_COMPUTE_HOST"] = host manifestdata = getManifestTemplate("nova_compute.pp") @@ -541,6 +592,7 @@ def createcomputemanifest(config): manifestdata += getManifestTemplate("firewall.pp") manifestdata += "\n" + nova_config_options.getManifestEntry() + manifestdata += "\n" + ssh_hostkeys appendManifestFile(manifestfile, manifestdata) diff --git a/packstack/puppet/templates/nova_common_qpid.pp b/packstack/puppet/templates/nova_common_qpid.pp index 94f43cdef..148ab04f3 100644 --- a/packstack/puppet/templates/nova_common_qpid.pp +++ b/packstack/puppet/templates/nova_common_qpid.pp @@ -9,4 +9,13 @@ class { "nova": qpid_protocol => '%(CONFIG_AMQP_PROTOCOL)s', verbose => true, debug => %(CONFIG_DEBUG_MODE)s, + nova_public_key => { + type => '%(NOVA_MIGRATION_KEY_TYPE)s', + key => '%(NOVA_MIGRATION_KEY_PUBLIC)s', + }, + nova_private_key => { + type => '%(NOVA_MIGRATION_KEY_TYPE)s', + key => '%(NOVA_MIGRATION_KEY_SECRET)s', + }, + nova_shell => '/bin/bash', } diff --git a/packstack/puppet/templates/nova_common_rabbitmq.pp b/packstack/puppet/templates/nova_common_rabbitmq.pp index 775f01d6c..a3511a273 100644 --- a/packstack/puppet/templates/nova_common_rabbitmq.pp +++ b/packstack/puppet/templates/nova_common_rabbitmq.pp @@ -7,4 +7,13 @@ class { "nova": rabbit_password => '%(CONFIG_AMQP_AUTH_PASSWORD)s', verbose => true, debug => %(CONFIG_DEBUG_MODE)s, + nova_public_key => { + type => '%(NOVA_MIGRATION_KEY_TYPE)s', + key => '%(NOVA_MIGRATION_KEY_PUBLIC)s', + }, + nova_private_key => { + type => '%(NOVA_MIGRATION_KEY_TYPE)s', + key => '%(NOVA_MIGRATION_KEY_SECRET)s', + }, + nova_shell => '/bin/bash', } diff --git a/packstack/puppet/templates/nova_compute.pp b/packstack/puppet/templates/nova_compute.pp index 213493786..f36cfcd15 100644 --- a/packstack/puppet/templates/nova_compute.pp +++ b/packstack/puppet/templates/nova_compute.pp @@ -2,8 +2,26 @@ package{'python-cinderclient': before => Class["nova"] } +# Install the private key to be used for live migration. This needs to be configured +# into libvirt/live_migration_uri in nova.conf. +file { '/etc/nova/ssh': + ensure => directory, + owner => root, + group => root, + mode => 0700, +} + +file { '/etc/nova/ssh/nova_migration_key': + content => '%(NOVA_MIGRATION_KEY_SECRET)s', + mode => 0600, + owner => root, + group => root, + require => File['/etc/nova/ssh'], +} + nova_config{ - "DEFAULT/volume_api_class": value => "nova.volume.cinder.API"; + "DEFAULT/volume_api_class": value => "nova.volume.cinder.API"; + "libvirt/live_migration_uri": value => "qemu+ssh://nova@%%s/system?keyfile=/etc/nova/ssh/nova_migration_key"; } class {"nova::compute": @@ -56,3 +74,4 @@ exec {'tuned-virtual-host': command => '/usr/sbin/tuned-adm profile virtual-host', require => Service['tuned'], } + diff --git a/packstack/puppet/templates/nova_compute_libvirt.pp b/packstack/puppet/templates/nova_compute_libvirt.pp index 96b3251e0..97ad5f591 100644 --- a/packstack/puppet/templates/nova_compute_libvirt.pp +++ b/packstack/puppet/templates/nova_compute_libvirt.pp @@ -24,7 +24,7 @@ exec { 'qemu-kvm': class { 'nova::compute::libvirt': libvirt_type => "$libvirt_type", - vncserver_listen => "%(CONFIG_NOVA_COMPUTE_HOST)s", + vncserver_listen => "0.0.0.0", } exec {'load_kvm': diff --git a/packstack/puppet/templates/sshkey.pp b/packstack/puppet/templates/sshkey.pp new file mode 100644 index 000000000..f1fd66e0b --- /dev/null +++ b/packstack/puppet/templates/sshkey.pp @@ -0,0 +1,7 @@ +sshkey { '%(SSH_HOST_NAME)s': + ensure => present, + host_aliases => [%(SSH_HOST_ALIASES)s], + key => '%(SSH_HOST_KEY)s', + type => '%(SSH_HOST_KEY_TYPE)s', +} + diff --git a/tests/installer/test_run_setup.py b/tests/installer/test_run_setup.py index 8e91bfa00..d104b1854 100644 --- a/tests/installer/test_run_setup.py +++ b/tests/installer/test_run_setup.py @@ -23,11 +23,16 @@ from unittest import TestCase from packstack.modules import ospluginutils, puppet from packstack.installer import run_setup, basedefs -from packstack.installer.utils import shell from ..test_base import PackstackTestCaseMixin, FakePopen +def makefile(path, content): + '''Create the named file with the specified content.''' + with open(path, 'w') as fd: + fd.write(content) + + class CommandLineTestCase(PackstackTestCaseMixin, TestCase): def test_running_install_hosts(self): """ @@ -44,11 +49,12 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase): Popen is replaced in PackstackTestCaseMixin so no actual commands get run on the host running the unit tests """ - # we need following to pass manage_epel(enabled=1) and - # manage_rdo(havana-6.noarch\nenabled=0) functions + subprocess.Popen = FakePopen FakePopen.register('cat /etc/resolv.conf | grep nameserver', stdout='nameserver 127.0.0.1') + + # required by packstack.plugins.serverprep_949.mangage_rdo FakePopen.register("rpm -q rdo-release " "--qf='%{version}-%{release}.%{arch}\n'", stdout='icehouse-2.noarch\n') @@ -56,10 +62,21 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase): 'openstack-icehouse', stdout='[openstack-icehouse]\nenabled=1') + # required by packstack.plugins.nova_300.gather_host_keys + FakePopen.register('ssh-keyscan 127.0.0.1', + stdout='127.0.0.1 ssh-rsa hostkey-data') + # create a dummy public key dummy_public_key = os.path.join(self.tempdir, 'id_rsa.pub') - with open(dummy_public_key, 'w') as dummy: - dummy.write('ssh-rsa AAAAblablabla') + makefile(dummy_public_key, 'ssh-rsa AAAAblablabla') + + # create dummy keys for live migration mechanism + makefile(os.path.join(basedefs.VAR_DIR, 'nova_migration_key'), + '-----BEGIN RSA PRIVATE KEY-----\n' + 'keydata\n' + '-----END RSA PRIVATE KEY-----\n') + makefile(os.path.join(basedefs.VAR_DIR, 'nova_migration_key.pub'), + 'ssh-rsa keydata') # Save sys.argv and replace it with the args we want optparse to use orig_argv = sys.argv