blob: 9784a74d552c506115326056b5dbef1b3b962c03 [file] [log] [blame]
"""Guest definition.
"""
# pylint: disable=too-many-instance-attributes,no-self-use
import os
from xml.etree import ElementTree as ET
import glog
class GuestDefinition(object):
"""Guest resource requirements definition.
Args:
lvc LibVirtClient instance.
"""
def __init__(self, lvc):
self._cmdline = None
self._initrd = None
self._instance_name = None
self._instance_id = None
self._kernel = None
self._memory_mb = None
self._net_mobile_bridge = None
self._iv_vectors = None
self._iv_socket_path = None
self._iv_socket_path = None
self._part_cache = None
self._part_data = None
self._part_ramdisk = None
self._part_system = None
self._vcpus = None
self._vmm_path = None
# Initialize the following fields. These should not be configured by user.
self._num_net_interfaces = 0
self._num_ttys_interfaces = 0
self._num_virtio_channels = 0
self._lvc = lvc
self.set_instance_id(1)
self.set_num_vcpus(1)
self.set_memory_mb(512)
self.set_kernel('vmlinuz')
self.set_initrd('initrd.img')
self.set_net_mobile_bridge(None)
def set_instance_id(self, inst_id):
"""Set instance ID of this guest.
Args:
inst_id Numerical instance ID, starting at 1.
"""
if inst_id < 1 or inst_id > 255:
glog.error('Ignoring invalid instance id requested: %d.', inst_id)
return
self._instance_id = inst_id
self._instance_name = None
def get_instance_name(self):
"""Return name of this instance.
"""
if self._instance_name is None:
self._instance_name = self._lvc.build_instance_name(self._instance_id)
return self._instance_name
def set_num_vcpus(self, cpus):
"""Set number of virtual CPUs for this guest.
Number of VCPUs will be checked for sanity and local maximums.
"""
max_cpus = self._lvc.get_max_vcpus()
if cpus < 0 or cpus > max_cpus:
glog.error('Ignoring invalid number of vcpus requested (%d); ' +
'max is %d.', cpus, max_cpus)
return
self._vcpus = cpus
def set_memory_mb(self, memory_mb):
"""Set memory size allocated for the quest in MB.
Args:
memory_mb Total amount of memory allocated for the guest in MB.
"""
if memory_mb < 0:
glog.error('Ignoring invalid amount of memory requested (%d).', memory_mb)
return
self._memory_mb = memory_mb
def set_kernel(self, kernel):
"""Specify kernel path.
Args:
kernel Path to vmlinuz file.
"""
if not kernel:
glog.error('Kernel path must be specified.')
return
self._kernel = kernel
def set_initrd(self, initrd):
"""Specify initrd path.
Args:
initrd Path to initrd.img file.
"""
if not initrd:
glog.error('Initial ramdisk path must be specified.')
return
self._initrd = initrd
def set_cmdline(self, cmdline):
"""Specify kernel command line arguments.
Args:
cmdline Additional kernel command line arguments.
"""
self._cmdline = cmdline
def set_cf_ramdisk_path(self, path):
"""Specify cuttlefish ramdisk path.
Args:
path Cuttlefish built 'ramdisk.img' path.
"""
if path is not None and not os.path.exists(path):
glog.warning("Cuttlefish ramdisk.img not found at %s", path)
self._part_ramdisk = path
def set_cf_system_path(self, path):
"""Specify cuttlefish system path.
Args:
path Cuttlefish built 'system.img' path.
"""
if path is not None and not os.path.exists(path):
glog.warning("Cuttlefish system.img not found at %s", path)
self._part_system = path
def set_cf_data_path(self, path):
"""Specify cuttlefish data path.
Args:
path Cuttlefish built 'data.img' path.
"""
if path is not None and not os.path.exists(path):
glog.warning("Cuttlefish data.img not found at %s", path)
self._part_data = path
def set_cf_cache_path(self, path):
"""Specify cuttlefish cache path.
Args:
path Cuttlefish built 'cache.img' path.
"""
if path is not None and not os.path.exists(path):
glog.warning("Cuttlefish cache.img not found at %s", path)
self._part_cache = path
def set_net_mobile_bridge(self, bridge):
"""Specify mobile network bridge name.
Args:
bridge Name of the mobile network bridge.
"""
if bridge is not None:
# TODO(ender): check if bridge exists.
pass
self._net_mobile_bridge = bridge
def set_vmm_path(self, path):
"""Specify path to virtual machine monitor that will be running our guest.
Args:
path Path to virtual machine monitor, eg. /usr/bin/qemu.
"""
self._vmm_path = path
def set_ivshmem_vectors(self, num):
"""Specify number of IV Shared Memory vectors.
Args:
num Number of vectors (non-negative).
"""
if num < 0:
glog.error('Invalid number of iv shared memory vectors: %d', num)
return
self._iv_vectors = num
def set_ivshmem_socket_path(self, path):
"""Specify path to unix socket managed by IV Shared Memory daemon.
Args:
path Path to unix domain socket.
"""
self._iv_socket_path = path
def _configure_vm(self, tree):
"""Create basic guest details.
Args:
tree Top level 'domain' element of the XML tree.
"""
ET.SubElement(tree, 'name').text = self.get_instance_name()
ET.SubElement(tree, 'on_poweroff').text = 'destroy'
ET.SubElement(tree, 'on_reboot').text = 'restart'
# TODO(ender): should this be restart?
ET.SubElement(tree, 'on_crash').text = 'destroy'
ET.SubElement(tree, 'vcpu').text = str(self._vcpus)
ET.SubElement(tree, 'memory').text = str(self._memory_mb << 10)
def _configure_kernel(self, tree):
"""Configure boot parameters for guest.
Args:
tree Top level 'domain' element of the XML tree.
"""
node = ET.SubElement(tree, 'os')
desc = ET.SubElement(node, 'type')
desc.set('arch', 'x86_64')
desc.set('machine', 'pc')
desc.text = 'hvm'
ET.SubElement(node, 'kernel').text = self._kernel
ET.SubElement(node, 'initrd').text = self._initrd
if self._cmdline is not None:
ET.SubElement(node, 'cmdline').text = self._cmdline
def _build_device_serial_port(self):
"""Configure serial ports for guest.
More useful information can be found here:
https://libvirt.org/formatdomain.html#elementCharSerial
"""
index = self._num_ttys_interfaces
self._num_ttys_interfaces += 1
path = '/tmp/%s-ttyS%d.log' % (self.get_instance_name(), index)
tty = ET.Element('serial')
tty.set('type', 'file')
src = ET.SubElement(tty, 'source')
src.set('path', path)
src.set('append', 'no')
ET.SubElement(tty, 'target').set('port', str(index))
glog.info('Serial port %d will send data to %s', index, path)
return tty
def _build_device_virtio_channel(self):
"""Build fast paravirtualized virtio channel.
More useful information can be found here:
https://libvirt.org/formatdomain.html#elementCharSerial
"""
index = self._num_virtio_channels
self._num_virtio_channels += 1
path = '/tmp/%s-vport0p%d.log' % (self.get_instance_name(), index)
vio = ET.Element('channel')
vio.set('type', 'file')
src = ET.SubElement(vio, 'source')
src.set('path', path)
src.set('append', 'no')
tgt = ET.SubElement(vio, 'target')
tgt.set('type', 'virtio')
tgt.set('name', 'vport0p%d' % index)
adr = ET.SubElement(vio, 'address')
adr.set('type', 'virtio-serial')
adr.set('controller', '0')
adr.set('bus', '0')
adr.set('port', str(index))
glog.info('Virtio channel %d will send data to %s', index, path)
return vio
def _build_device_disk_node(self, path, name, target_dev):
"""Create disk node for guest.
More useful information can be found here:
https://libvirt.org/formatdomain.html#elementsDisks
Args:
path Path to file containing partition or disk image.
name Purpose of partition or disk image.
target_dev Target device.
"""
bus = 'ide'
if target_dev.startswith('sd'):
bus = 'sata'
elif target_dev.startswith('vd'):
bus = 'virtio'
if path is None:
glog.fatal('No file specified for %s; (%s) %s is not available.',
name, bus, target_dev)
return None
disk = ET.Element('disk')
disk.set('type', 'file')
# disk.set('snapshot', 'external')
drvr = ET.SubElement(disk, 'driver')
drvr.set('name', 'qemu')
drvr.set('type', 'raw')
drvr.set('io', 'threads')
trgt = ET.SubElement(disk, 'target')
trgt.set('dev', target_dev)
trgt.set('bus', bus)
srce = ET.SubElement(disk, 'source')
srce.set('file', path)
return disk
def _build_mac_address(self, index):
"""Create mac address from local instance number.
"""
return '00:41:56:44:%02X:%02X' % (self._instance_id, index + 1)
def _build_device_net_node(self, local_node, bridge):
"""Create virtual ethernet for guest.
More useful information can be found here:
https://libvirt.org/formatdomain.html#elementsNICSVirtual
https://wiki.libvirt.org/page/Virtio
Args:
local_node Name of the local interface.
bridge Name of the corresponding bridge.
"""
index = self._num_net_interfaces
self._num_net_interfaces += 1
net = ET.Element('interface')
net.set('type', 'bridge')
ET.SubElement(net, 'source').set('bridge', bridge)
ET.SubElement(net, 'mac').set('address', self._build_mac_address(index))
ET.SubElement(net, 'model').set('type', 'e1000')
ET.SubElement(net, 'target').set('dev', '%s%d' % (local_node, self._instance_id))
return net
def _configure_devices(self, tree):
"""Configure guest devices.
Args:
tree Top level 'domain' element of the XML tree.
"""
dev = ET.SubElement(tree, 'devices')
if self._vmm_path:
ET.SubElement(dev, 'emulator').text = self._vmm_path
dev.append(self._build_device_serial_port())
dev.append(self._build_device_virtio_channel())
dev.append(self._build_device_disk_node(self._part_ramdisk, 'ramdisk', 'vda'))
dev.append(self._build_device_disk_node(self._part_system, 'system', 'vdb'))
dev.append(self._build_device_disk_node(self._part_data, 'data', 'vdc'))
dev.append(self._build_device_disk_node(self._part_cache, 'cache', 'vdd'))
dev.append(self._build_device_net_node('amobile', self._net_mobile_bridge))
def _configure_ivshmem(self, tree):
"""Configure InterVM Shared Memory region.
Args:
tree Top level 'domain' element of the XML tree.
"""
if self._iv_vectors:
cmd = ET.SubElement(tree, 'qemu:commandline')
ET.SubElement(cmd, 'qemu:arg').set('value', '-chardev')
ET.SubElement(cmd, 'qemu:arg').set(
'value', 'socket,path=%s,id=ivsocket' % (self._iv_socket_path))
ET.SubElement(cmd, 'qemu:arg').set('value', '-device')
ET.SubElement(cmd, 'qemu:arg').set(
'value', 'ivshmem-doorbell,chardev=ivsocket,vectors=%d' % (self._iv_vectors)
)
def to_xml(self):
"""Build XML document describing guest properties.
The created document will be used directly by libvirt to create corresponding
virtual machine.
Returns:
string containing an XML document describing a VM.
"""
tree = ET.Element('domain')
tree.set('type', 'kvm')
tree.set('xmlns:qemu', 'http://libvirt.org/schemas/domain/qemu/1.0')
self._configure_vm(tree)
self._configure_kernel(tree)
self._configure_devices(tree)
self._configure_ivshmem(tree)
return ET.tostring(tree).decode('utf-8')