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:
parent
4836eecd60
commit
207210f926
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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'],
|
||||
}
|
||||
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue