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 uuid
|
||||||
import logging
|
import logging
|
||||||
import platform
|
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.installer.exceptions import ScriptRuntimeError
|
||||||
|
|
||||||
from packstack.modules.shortcuts import get_mq
|
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 Keystone manifest entries', 'functions':[createkeystonemanifest]},
|
||||||
{'title': 'Adding Nova Cert manifest entries', 'functions':[createcertmanifest]},
|
{'title': 'Adding Nova Cert manifest entries', 'functions':[createcertmanifest]},
|
||||||
{'title': 'Adding Nova Conductor manifest entries', 'functions':[createconductormanifest]},
|
{'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 Compute manifest entries', 'functions':[createcomputemanifest]},
|
||||||
{'title': 'Adding Nova Scheduler manifest entries', 'functions':[createschedmanifest]},
|
{'title': 'Adding Nova Scheduler manifest entries', 'functions':[createschedmanifest]},
|
||||||
{'title': 'Adding Nova VNC Proxy manifest entries', 'functions':[createvncproxymanifest]},
|
{'title': 'Adding Nova VNC Proxy manifest entries', 'functions':[createvncproxymanifest]},
|
||||||
|
@ -496,8 +501,54 @@ def bring_up_ifcfg(host, device):
|
||||||
raise ScriptRuntimeError(msg)
|
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):
|
def createcomputemanifest(config):
|
||||||
global compute_hosts, network_hosts
|
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:
|
for host in compute_hosts:
|
||||||
config["CONFIG_NOVA_COMPUTE_HOST"] = host
|
config["CONFIG_NOVA_COMPUTE_HOST"] = host
|
||||||
manifestdata = getManifestTemplate("nova_compute.pp")
|
manifestdata = getManifestTemplate("nova_compute.pp")
|
||||||
|
@ -541,6 +592,7 @@ def createcomputemanifest(config):
|
||||||
manifestdata += getManifestTemplate("firewall.pp")
|
manifestdata += getManifestTemplate("firewall.pp")
|
||||||
|
|
||||||
manifestdata += "\n" + nova_config_options.getManifestEntry()
|
manifestdata += "\n" + nova_config_options.getManifestEntry()
|
||||||
|
manifestdata += "\n" + ssh_hostkeys
|
||||||
appendManifestFile(manifestfile, manifestdata)
|
appendManifestFile(manifestfile, manifestdata)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,13 @@ class { "nova":
|
||||||
qpid_protocol => '%(CONFIG_AMQP_PROTOCOL)s',
|
qpid_protocol => '%(CONFIG_AMQP_PROTOCOL)s',
|
||||||
verbose => true,
|
verbose => true,
|
||||||
debug => %(CONFIG_DEBUG_MODE)s,
|
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',
|
rabbit_password => '%(CONFIG_AMQP_AUTH_PASSWORD)s',
|
||||||
verbose => true,
|
verbose => true,
|
||||||
debug => %(CONFIG_DEBUG_MODE)s,
|
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"]
|
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{
|
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":
|
class {"nova::compute":
|
||||||
|
@ -56,3 +74,4 @@ exec {'tuned-virtual-host':
|
||||||
command => '/usr/sbin/tuned-adm profile virtual-host',
|
command => '/usr/sbin/tuned-adm profile virtual-host',
|
||||||
require => Service['tuned'],
|
require => Service['tuned'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ exec { 'qemu-kvm':
|
||||||
|
|
||||||
class { 'nova::compute::libvirt':
|
class { 'nova::compute::libvirt':
|
||||||
libvirt_type => "$libvirt_type",
|
libvirt_type => "$libvirt_type",
|
||||||
vncserver_listen => "%(CONFIG_NOVA_COMPUTE_HOST)s",
|
vncserver_listen => "0.0.0.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
exec {'load_kvm':
|
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.modules import ospluginutils, puppet
|
||||||
from packstack.installer import run_setup, basedefs
|
from packstack.installer import run_setup, basedefs
|
||||||
from packstack.installer.utils import shell
|
|
||||||
|
|
||||||
from ..test_base import PackstackTestCaseMixin, FakePopen
|
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):
|
class CommandLineTestCase(PackstackTestCaseMixin, TestCase):
|
||||||
def test_running_install_hosts(self):
|
def test_running_install_hosts(self):
|
||||||
"""
|
"""
|
||||||
|
@ -44,11 +49,12 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase):
|
||||||
Popen is replaced in PackstackTestCaseMixin so no actual commands get
|
Popen is replaced in PackstackTestCaseMixin so no actual commands get
|
||||||
run on the host running the unit tests
|
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
|
subprocess.Popen = FakePopen
|
||||||
FakePopen.register('cat /etc/resolv.conf | grep nameserver',
|
FakePopen.register('cat /etc/resolv.conf | grep nameserver',
|
||||||
stdout='nameserver 127.0.0.1')
|
stdout='nameserver 127.0.0.1')
|
||||||
|
|
||||||
|
# required by packstack.plugins.serverprep_949.mangage_rdo
|
||||||
FakePopen.register("rpm -q rdo-release "
|
FakePopen.register("rpm -q rdo-release "
|
||||||
"--qf='%{version}-%{release}.%{arch}\n'",
|
"--qf='%{version}-%{release}.%{arch}\n'",
|
||||||
stdout='icehouse-2.noarch\n')
|
stdout='icehouse-2.noarch\n')
|
||||||
|
@ -56,10 +62,21 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase):
|
||||||
'openstack-icehouse',
|
'openstack-icehouse',
|
||||||
stdout='[openstack-icehouse]\nenabled=1')
|
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
|
# create a dummy public key
|
||||||
dummy_public_key = os.path.join(self.tempdir, 'id_rsa.pub')
|
dummy_public_key = os.path.join(self.tempdir, 'id_rsa.pub')
|
||||||
with open(dummy_public_key, 'w') as dummy:
|
makefile(dummy_public_key, 'ssh-rsa AAAAblablabla')
|
||||||
dummy.write('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
|
# Save sys.argv and replace it with the args we want optparse to use
|
||||||
orig_argv = sys.argv
|
orig_argv = sys.argv
|
||||||
|
|
Loading…
Reference in New Issue