nbd_image_export.py 8.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
"""
Module for providing interfaces for exporting local image with
qemu-nbd and qemu internal nbd server.

Available classes:
- QemuNBDExportImage: Export local image with qemu-nbd
- InternalNBDExportImage: Export image with vm qemu internal nbd server

Available methods:
- create_image: Create a local image with qemu-img or
                with user defined command
- export_image: Export the local image
- stop_export: Stop exporting image
- list_exported_image: List nbd image with qemu-nbd
- hotplug_tls: Hotplug tls creds object for internal nbd server
- hotplug_image: Hotplug local image to be exported
- get_export_name: Get export name for internal nbd export
- start_nbd_server: Start internal nbd server
- add_nbd_image: Add image to internal nbd server
- remove_nbd_image: Remove image from internal nbd server
- stop_nbd_server: Stop internal nbd server
"""

import os
import signal
import logging

from avocado.utils import process
from avocado.core import exceptions

from virttest import nbd
from virttest import data_dir
from virttest import qemu_storage
from virttest import utils_misc
from virttest import qemu_devices


class NBDExportImage(object):
    """NBD local image export base class"""

    def __init__(self, params, local_image):
        """
        Initialize object.
        :param local_image: local image tag
        :param params: dictionary containing all test parameters.
        """
        self._tag = local_image
        self._params = params
        self._image_params = self._params.object_params(self._tag)

    def create_image(self):
        if self._image_params.get('create_image_cmd'):
            result = process.run(self._image_params['create_image_cmd'],
                                 ignore_status=True, shell=True)
        elif not self._image_params.get_boolean("force_create_image"):
56 57 58 59 60
            _, result = qemu_storage.QemuImg(
                self._image_params,
                data_dir.get_data_dir(),
                self._tag
            ).create(self._image_params)
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

        if result.exit_status != 0:
            raise exceptions.TestFail('Failed to create image, error: %s'
                                      % result.stderr.decode())

    def export_image(self):
        raise NotImplementedError()

    def stop_export(self):
        raise NotImplementedError()


class QemuNBDExportImage(NBDExportImage):
    """Export local image with qemu-nbd command"""

    def __init__(self, params, local_image):
        super(QemuNBDExportImage, self).__init__(params, local_image)
        self._qemu_nbd = utils_misc.get_qemu_nbd_binary(self._params)
        filename_repr = 'json' if self._image_params.get(
            'nbd_export_format') == 'luks' else 'filename'
        self._local_filename = qemu_storage.get_image_repr(
            self._tag, self._image_params,
            data_dir.get_data_dir(), filename_repr)
        self._nbd_server_pid = None

    def export_image(self):
        logging.info("Export image with qemu-nbd")
        self._nbd_server_pid = nbd.export_image(self._qemu_nbd,
                                                self._local_filename,
                                                self._tag, self._image_params)
        if self._nbd_server_pid is None:
            raise exceptions.TestFail('Failed to export image')

    def list_exported_image(self, nbd_image, nbd_image_params):
        logging.info("List the nbd image with qemu-nbd")
        result = nbd.list_exported_image(self._qemu_nbd, nbd_image,
                                         nbd_image_params)
        if result.exit_status != 0:
            raise exceptions.TestFail('Failed to list nbd image: %s'
                                      % result.stderr.decode())

    def stop_export(self):
        if self._nbd_server_pid is not None:
            try:
                # when qemu-nbd crashes unexpectedly, we can handle it
                os.kill(self._nbd_server_pid, signal.SIGKILL)
            except Exception as e:
                logging.warn("Error occurred when killing nbd server: %s"
                             % str(e))
            finally:
                self._nbd_server_pid = None


class InternalNBDExportImage(NBDExportImage):
    """Export image with qemu internal nbd server"""

    def __init__(self, vm, params, local_image):
        super(InternalNBDExportImage, self).__init__(params, local_image)
        self._tls_creds_id = None
        self._node_name = None
        self._image_devices = None
        self._vm = vm

    def get_export_name(self):
        """export name is the node name if nbd_export_name is not set"""
        return self._image_params['nbd_export_name'] if self._image_params.get(
            'nbd_export_name') else self._node_name

    def hotplug_image(self):
        """Hotplug the image to be exported"""
        devices = self._vm.devices.images_define_by_params(self._tag,
                                                           self._image_params,
                                                           'disk')

        # Only hotplug protocol and format node and the related objects
        devices.pop()
        self._node_name = devices[-1].get_qid()
        self._image_devices = devices

        logging.info("Plug devices(without image device driver)")
        for dev in devices:
            ret = self._vm.devices.simple_hotplug(dev, self._vm.monitor)
            if not ret[1]:
                raise exceptions.TestFail("Failed to hotplug device '%s': %s."
                                          % (dev, ret[0]))

    def hotplug_tls(self):
        """Hotplug tls creds object for nbd server"""
        if self._image_params.get('nbd_unix_socket'):
            logging.info('TLS is only supported with IP')
        elif self._image_params.get('nbd_server_tls_creds'):
            logging.info("Plug server tls creds device")
            self._tls_creds_id = '%s_server_tls_creds' % self._tag
            dev = qemu_devices.qdevices.QObject('tls-creds-x509')
            dev.set_param("id", self._tls_creds_id)
            dev.set_param("endpoint", "server")
            dev.set_param("dir", self._image_params['nbd_server_tls_creds'])
            ret = self._vm.devices.simple_hotplug(dev, self._vm.monitor)
            if not ret[1]:
                raise exceptions.TestFail("Failed to hotplug device '%s': %s."
                                          % (dev, ret[0]))

    def start_nbd_server(self):
        """Start internal nbd server"""
        server = {
            'type': 'unix',
            'path': self._image_params['nbd_unix_socket']
        } if self._image_params.get('nbd_unix_socket') else {
            'type': 'inet',
            'host': '0.0.0.0',
            'port': self._image_params.get('nbd_port', '10809')
        }

        logging.info("Start internal nbd server")
        return self._vm.monitor.nbd_server_start(server, self._tls_creds_id)

    def add_nbd_image(self, node_name=None):
        """
        Add an image(to be exported) to internal nbd server.
        :param node_name: block node name, the node might be hotplugged
                          by other utils, or the node has already been
                          present in VM.
        """
        if node_name:
            self._node_name = node_name

        logging.info("Add image node to nbd server")
        return self._vm.monitor.nbd_server_add(
            self._node_name,
            self._image_params.get('nbd_export_name'),
            self._image_params.get('nbd_export_writable'),
            self._image_params.get('nbd_export_bitmap'))

    def remove_nbd_image(self):
        """Remove the exported image from internal nbd server"""
        logging.info("Remove image from nbd server")
        return self._vm.monitor.nbd_server_remove(
            self.get_export_name(),
            self._image_params.get('nbd_remove_mode')
        )

    def stop_nbd_server(self):
        """Stop internal nbd server, it also unregisters all devices"""
        logging.info("Stop nbd server")
        return self._vm.monitor.nbd_server_stop()

    def export_image(self):
        """
        For internal nbd server, in order to export an image, start the
        internal nbd server first, then add a local image to server.
        """
        self.start_nbd_server()
        self.add_nbd_image()

    def stop_export(self):
        self.remove_nbd_image()
        self.stop_nbd_server()