Blame tests/cloud_tests/platforms/nocloudkvm/instance.py

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