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

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