|
Packit Service |
a04d08 |
# This file is part of cloud-init. See LICENSE file for license information.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
"""Base NoCloud KVM instance."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
import copy
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
import socket
|
|
Packit Service |
a04d08 |
import subprocess
|
|
Packit Service |
a04d08 |
import time
|
|
Packit Service |
a04d08 |
import uuid
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from ..instances import Instance
|
|
Packit Service |
a04d08 |
from cloudinit.atomic_helper import write_json
|
|
Packit Service |
11b429 |
from cloudinit import util as c_util
|
|
Packit Service |
a04d08 |
from tests.cloud_tests import LOG, util
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# This domain contains reverse lookups for hostnames that are used.
|
|
Packit Service |
a04d08 |
# The primary reason is so sudo will return quickly when it attempts
|
|
Packit Service |
a04d08 |
# to look up the hostname. i9n is just short for 'integration'.
|
|
Packit Service |
a04d08 |
# see also bug 1730744 for why we had to do this.
|
|
Packit Service |
a04d08 |
CI_DOMAIN = "i9n.cloud-init.io"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class NoCloudKVMInstance(Instance):
|
|
Packit Service |
a04d08 |
"""NoCloud KVM backed instance."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
platform_name = "nocloud-kvm"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, platform, name, image_path, properties, config,
|
|
Packit Service |
a04d08 |
features, user_data, meta_data):
|
|
Packit Service |
a04d08 |
"""Set up instance.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param platform: platform object
|
|
Packit Service |
a04d08 |
@param name: image path
|
|
Packit Service |
a04d08 |
@param image_path: path to disk image to boot.
|
|
Packit Service |
a04d08 |
@param properties: dictionary of properties
|
|
Packit Service |
a04d08 |
@param config: dictionary of configuration values
|
|
Packit Service |
a04d08 |
@param features: dictionary of supported feature flags
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
super(NoCloudKVMInstance, self).__init__(
|
|
Packit Service |
a04d08 |
platform, name, properties, config, features
|
|
Packit Service |
a04d08 |
)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
self.user_data = user_data
|
|
Packit Service |
a04d08 |
if meta_data:
|
|
Packit Service |
a04d08 |
meta_data = copy.deepcopy(meta_data)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
meta_data = {}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if 'instance-id' in meta_data:
|
|
Packit Service |
a04d08 |
iid = meta_data['instance-id']
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
iid = str(uuid.uuid1())
|
|
Packit Service |
a04d08 |
meta_data['instance-id'] = iid
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
self.instance_id = iid
|
|
Packit Service |
a04d08 |
self.ssh_key_file = os.path.join(
|
|
Packit Service |
a04d08 |
platform.config['data_dir'], platform.config['private_key'])
|
|
Packit Service |
a04d08 |
self.ssh_pubkey_file = os.path.join(
|
|
Packit Service |
a04d08 |
platform.config['data_dir'], platform.config['public_key'])
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
self.ssh_pubkey = None
|
|
Packit Service |
a04d08 |
if self.ssh_pubkey_file:
|
|
Packit Service |
a04d08 |
with open(self.ssh_pubkey_file, "r") as fp:
|
|
Packit Service |
a04d08 |
self.ssh_pubkey = fp.read().rstrip('\n')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if not meta_data.get('public-keys'):
|
|
Packit Service |
a04d08 |
meta_data['public-keys'] = []
|
|
Packit Service |
a04d08 |
meta_data['public-keys'].append(self.ssh_pubkey)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
self.ssh_ip = '127.0.0.1'
|
|
Packit Service |
a04d08 |
self.ssh_port = None
|
|
Packit Service |
a04d08 |
self.pid = None
|
|
Packit Service |
a04d08 |
self.pid_file = None
|
|
Packit Service |
a04d08 |
self.console_file = None
|
|
Packit Service |
a04d08 |
self.disk = image_path
|
|
Packit Service |
a04d08 |
self.cache_mode = platform.config.get('cache_mode',
|
|
Packit Service |
a04d08 |
'cache=none,aio=native')
|
|
Packit Service |
a04d08 |
self.meta_data = meta_data
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def shutdown(self, wait=True):
|
|
Packit Service |
a04d08 |
"""Shutdown instance."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if self.pid:
|
|
Packit Service |
a04d08 |
# This relies on _execute which uses sudo over ssh. The ssh
|
|
Packit Service |
a04d08 |
# connection would get killed before sudo exited, so ignore errors.
|
|
Packit Service |
a04d08 |
cmd = ['shutdown', 'now']
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
self._execute(cmd)
|
|
Packit Service |
a04d08 |
except util.InTargetExecuteError:
|
|
Packit Service |
a04d08 |
pass
|
|
Packit Service |
a04d08 |
self._ssh_close()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if wait:
|
|
Packit Service |
a04d08 |
LOG.debug("Executed shutdown. waiting on pid %s to end",
|
|
Packit Service |
a04d08 |
self.pid)
|
|
Packit Service |
a04d08 |
time_for_shutdown = 120
|
|
Packit Service |
a04d08 |
give_up_at = time.time() + time_for_shutdown
|
|
Packit Service |
a04d08 |
pid_file_path = '/proc/%s' % self.pid
|
|
Packit Service |
a04d08 |
msg = ("pid %s did not exit in %s seconds after shutdown." %
|
|
Packit Service |
a04d08 |
(self.pid, time_for_shutdown))
|
|
Packit Service |
a04d08 |
while True:
|
|
Packit Service |
a04d08 |
if not os.path.exists(pid_file_path):
|
|
Packit Service |
a04d08 |
break
|
|
Packit Service |
a04d08 |
if time.time() > give_up_at:
|
|
Packit Service |
a04d08 |
raise util.PlatformError("shutdown", msg)
|
|
Packit Service |
a04d08 |
self.pid = None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def destroy(self):
|
|
Packit Service |
a04d08 |
"""Clean up instance."""
|
|
Packit Service |
a04d08 |
if self.pid:
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
11b429 |
c_util.subp(['kill', '-9', self.pid])
|
|
Packit Service |
11b429 |
except c_util.ProcessExecutionError:
|
|
Packit Service |
a04d08 |
pass
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if self.pid_file:
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
os.remove(self.pid_file)
|
|
Packit Service |
a04d08 |
except Exception:
|
|
Packit Service |
a04d08 |
pass
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
self.pid = None
|
|
Packit Service |
a04d08 |
self._ssh_close()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
super(NoCloudKVMInstance, self).destroy()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _execute(self, command, stdin=None, env=None):
|
|
Packit Service |
a04d08 |
env_args = []
|
|
Packit Service |
a04d08 |
if env:
|
|
Packit Service |
a04d08 |
env_args = ['env'] + ["%s=%s" for k, v in env.items()]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return self._ssh(['sudo'] + env_args + list(command), stdin=stdin)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def generate_seed(self, tmpdir):
|
|
Packit Service |
a04d08 |
"""Generate nocloud seed from user-data"""
|
|
Packit Service |
a04d08 |
seed_file = os.path.join(tmpdir, '%s_seed.img' % self.name)
|
|
Packit Service |
a04d08 |
user_data_file = os.path.join(tmpdir, '%s_user_data' % self.name)
|
|
Packit Service |
a04d08 |
meta_data_file = os.path.join(tmpdir, '%s_meta_data' % self.name)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
with open(user_data_file, "w") as ud_file:
|
|
Packit Service |
a04d08 |
ud_file.write(self.user_data)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# meta-data can be yaml, but more easily pretty printed with json
|
|
Packit Service |
a04d08 |
write_json(meta_data_file, self.meta_data)
|
|
Packit Service |
11b429 |
c_util.subp(['cloud-localds', seed_file, user_data_file,
|
|
Packit Service |
11b429 |
meta_data_file])
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return seed_file
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_free_port(self):
|
|
Packit Service |
a04d08 |
"""Get a free port assigned by the kernel."""
|
|
Packit Service |
a04d08 |
s = socket.socket()
|
|
Packit Service |
a04d08 |
s.bind(('', 0))
|
|
Packit Service |
a04d08 |
num = s.getsockname()[1]
|
|
Packit Service |
a04d08 |
s.close()
|
|
Packit Service |
a04d08 |
return num
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def start(self, wait=True, wait_for_cloud_init=False):
|
|
Packit Service |
a04d08 |
"""Start instance."""
|
|
Packit Service |
a04d08 |
tmpdir = self.platform.config['data_dir']
|
|
Packit Service |
a04d08 |
seed = self.generate_seed(tmpdir)
|
|
Packit Service |
a04d08 |
self.pid_file = os.path.join(tmpdir, '%s.pid' % self.name)
|
|
Packit Service |
a04d08 |
self.console_file = os.path.join(tmpdir, '%s-console.log' % self.name)
|
|
Packit Service |
a04d08 |
self.ssh_port = self.get_free_port()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
cmd = ['./tools/xkvm',
|
|
Packit Service |
a04d08 |
'--disk', '%s,%s' % (self.disk, self.cache_mode),
|
|
Packit Service |
a04d08 |
'--disk', '%s' % seed,
|
|
Packit Service |
a04d08 |
'--netdev', ','.join(['user',
|
|
Packit Service |
a04d08 |
'hostfwd=tcp::%s-:22' % self.ssh_port,
|
|
Packit Service |
a04d08 |
'dnssearch=%s' % CI_DOMAIN]),
|
|
Packit Service |
a04d08 |
'--', '-pidfile', self.pid_file, '-vnc', 'none',
|
|
Packit Service |
a04d08 |
'-m', '2G', '-smp', '2', '-nographic', '-name', self.name,
|
|
Packit Service |
a04d08 |
'-serial', 'file:' + self.console_file]
|
|
Packit Service |
a04d08 |
subprocess.Popen(cmd,
|
|
Packit Service |
a04d08 |
close_fds=True,
|
|
Packit Service |
a04d08 |
stdin=subprocess.PIPE,
|
|
Packit Service |
a04d08 |
stdout=subprocess.PIPE,
|
|
Packit Service |
a04d08 |
stderr=subprocess.PIPE)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
while not os.path.exists(self.pid_file):
|
|
Packit Service |
a04d08 |
time.sleep(1)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
with open(self.pid_file, 'r') as pid_f:
|
|
Packit Service |
a04d08 |
self.pid = pid_f.readlines()[0].strip()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if wait:
|
|
Packit Service |
a04d08 |
self._wait_for_system(wait_for_cloud_init)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def console_log(self):
|
|
Packit Service |
a04d08 |
if not self.console_file:
|
|
Packit Service |
a04d08 |
return b''
|
|
Packit Service |
a04d08 |
with open(self.console_file, "rb") as fp:
|
|
Packit Service |
a04d08 |
return fp.read()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|