support ssh-based live migration

This change introduces support for ssh key generation and distribution
to support instance migration via qemu+ssh connections to remote
hypervisors.

This change requires https://review.openstack.org/#/c/89620/ (which
introduces support for ssh keys into puppet-nova)

Change-Id: I38c2aee3a7415bb69a099419f6149151c7d6b52c
Closes-bug: 1311168
This commit is contained in:
Lars Kellogg-Stedman 2014-04-10 11:36:16 -04:00
parent 4836eecd60
commit 207210f926
7 changed files with 121 additions and 8 deletions

View File

@ -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)

View File

@ -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',
}

View File

@ -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',
}

View File

@ -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'],
}

View File

@ -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':

View File

@ -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',
}

View File

@ -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