return self.data == other.data
-class QmpConnection:
- """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
-
- """
- _FIRST_MESSAGE_KEY = "QMP"
- _EVENT_KEY = "event"
- _ERROR_KEY = "error"
- _RETURN_KEY = RETURN_KEY = "return"
- _ACTUAL_KEY = ACTUAL_KEY = "actual"
- _ERROR_CLASS_KEY = "class"
- _ERROR_DATA_KEY = "data"
- _ERROR_DESC_KEY = "desc"
- _EXECUTE_KEY = "execute"
- _ARGUMENTS_KEY = "arguments"
- _CAPABILITIES_COMMAND = "qmp_capabilities"
- _MESSAGE_END_TOKEN = "\r\n"
+class MonitorSocket(object):
_SOCKET_TIMEOUT = 5
def __init__(self, monitor_filename):
- """Instantiates the QmpConnection object.
+ """Instantiates the MonitorSocket object.
@type monitor_filename: string
@param monitor_filename: the filename of the UNIX raw socket on which the
- QMP monitor is listening
+ monitor (QMP or simple one) is listening
"""
self.monitor_filename = monitor_filename
# in a reasonable amount of time
self.sock.settimeout(self._SOCKET_TIMEOUT)
self._connected = False
- self._buf = ""
def _check_socket(self):
sock_stat = None
sock_stat = os.stat(self.monitor_filename)
except EnvironmentError, err:
if err.errno == errno.ENOENT:
- raise errors.HypervisorError("No qmp socket found")
+ raise errors.HypervisorError("No monitor socket found")
else:
- raise errors.HypervisorError("Error checking qmp socket: %s",
+ raise errors.HypervisorError("Error checking monitor socket: %s",
utils.ErrnoOrStr(err))
if not stat.S_ISSOCK(sock_stat.st_mode):
- raise errors.HypervisorError("Qmp socket is not a socket")
+ raise errors.HypervisorError("Monitor socket is not a socket")
def _check_connection(self):
"""Make sure that the connection is established.
"""
if not self._connected:
- raise errors.ProgrammerError("To use a QmpConnection you need to first"
+ raise errors.ProgrammerError("To use a MonitorSocket you need to first"
" invoke connect() on it")
def connect(self):
- """Connects to the QMP monitor.
+ """Connects to the monitor.
- Connects to the UNIX socket and makes sure that we can actually send and
- receive data to the kvm instance via QMP.
+ Connects to the UNIX socket
@raise errors.HypervisorError: when there are communication errors
- @raise errors.ProgrammerError: when there are data serialization errors
"""
if self._connected:
raise errors.HypervisorError("Can't connect to qmp socket")
self._connected = True
+ def close(self):
+ """Closes the socket
+
+ It cannot be used after this call.
+
+ """
+ self.sock.close()
+
+
+class QmpConnection(MonitorSocket):
+ """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
+
+ """
+ _FIRST_MESSAGE_KEY = "QMP"
+ _EVENT_KEY = "event"
+ _ERROR_KEY = "error"
+ _RETURN_KEY = RETURN_KEY = "return"
+ _ACTUAL_KEY = ACTUAL_KEY = "actual"
+ _ERROR_CLASS_KEY = "class"
+ _ERROR_DATA_KEY = "data"
+ _ERROR_DESC_KEY = "desc"
+ _EXECUTE_KEY = "execute"
+ _ARGUMENTS_KEY = "arguments"
+ _CAPABILITIES_COMMAND = "qmp_capabilities"
+ _MESSAGE_END_TOKEN = "\r\n"
+
+ def __init__(self, monitor_filename):
+ super(QmpConnection, self).__init__(monitor_filename)
+ self._buf = ""
+
+ def connect(self):
+ """Connects to the QMP monitor.
+
+ Connects to the UNIX socket and makes sure that we can actually send and
+ receive data to the kvm instance via QMP.
+
+ @raise errors.HypervisorError: when there are communication errors
+ @raise errors.ProgrammerError: when there are data serialization errors
+
+ """
+ super(QmpConnection, self).connect()
# Check if we receive a correct greeting message from the server
# (As per the QEMU Protocol Specification 0.1 - section 2.2)
greeting = self._Recv()
data.append(info)
return data
- def _GenerateKVMBlockDevicesOptions(self, instance, block_devices, kvmhelp):
+ def _GenerateKVMBlockDevicesOptions(self, instance, block_devices,
+ kvmhelp, devlist):
+ """Generate KVM options regarding instance's block devices.
+
+ @type instance: L{objects.Instance}
+ @param instance: the instance object
+ @type block_devices: list of tuples
+ @param block_devices: list of tuples [(disk, link_name, uri)..]
+ @type kvmhelp: string
+ @param kvmhelp: output of kvm --help
+ @type devlist: string
+ @param devlist: output of kvm -device ?
+ @rtype: list
+ @return: list of command line options eventually used by kvm executable
+ """
hvp = instance.hvparams
- boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
+ kernel_path = hvp[constants.HV_KERNEL_PATH]
+ if kernel_path:
+ boot_disk = False
+ else:
+ boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
kvm_path = hvp[constants.HV_KVM_PATH]
# whether this is an older KVM version that uses the boot=on flag
if disk_type == constants.HT_DISK_PARAVIRTUAL:
if_val = ",if=%s" % self._VIRTIO
try:
- devlist = self._GetKVMOutput(kvm_path, self._KVMOPT_DEVICELIST)
if self._VIRTIO_BLK_RE.search(devlist):
if_val = ",if=none"
# will be passed in -device option as driver
# related parameters we'll use up_hvp
tapfds = []
taps = []
+ devlist = self._GetKVMOutput(kvm_path, self._KVMOPT_DEVICELIST)
if not kvm_nics:
kvm_cmd.extend(["-net", "none"])
else:
if nic_type == constants.HT_NIC_PARAVIRTUAL:
nic_model = self._VIRTIO
try:
- devlist = self._GetKVMOutput(kvm_path, self._KVMOPT_DEVICELIST)
if self._VIRTIO_NET_RE.search(devlist):
nic_model = self._VIRTIO_NET_PCI
vnet_hdr = up_hvp[constants.HV_VNET_HDR]
bdev_opts = self._GenerateKVMBlockDevicesOptions(instance,
block_devices,
- kvmhelp)
+ kvmhelp,
+ devlist)
kvm_cmd.extend(bdev_opts)
# CPU affinity requires kvm to start paused, so we set this flag if the
# instance is not already paused and if we are not going to accept a
dev.pci = int(free)
- def HotplugSupported(self, instance, action, dev_type):
- """Check if hotplug is supported.
+ def VerifyHotplugSupport(self, instance, action, dev_type):
+ """Verifies that hotplug is supported.
Hotplug is *not* supported in case of:
- qemu versions < 1.0
- security models and chroot (disk hotplug)
- fdsend module is missing (nic hot-add)
- @raise errors.HypervisorError: in previous cases
+ @raise errors.HypervisorError: in one of the previous cases
"""
output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
if not match:
raise errors.HotplugError("Try hotplug only in running instances.")
v_major, v_min, _, _ = match.groups()
- if (v_major, v_min) < (1, 0):
+ if (int(v_major), int(v_min)) < (1, 0):
raise errors.HotplugError("Hotplug not supported for qemu versions < 1.0")
if dev_type == constants.HOTPLUG_TARGET_DISK:
action == constants.HOTPLUG_ACTION_ADD and not fdsend):
raise errors.HotplugError("Cannot hot-add NIC."
" fdsend python module is missing.")
- return True
def _CallHotplugCommand(self, name, cmd):
output = self._CallMonitorCommand(name, cmd)
for line in output.stdout.splitlines():
logging.info("%s", line)
+ def HotAddDevice(self, instance, dev_type, device, extra, seq):
+ """ Helper method to hot-add a new device
+
+ It gets free pci slot generates the device name and invokes the
+ device specific method.
+
+ """
+ # in case of hot-mod this is given
+ if device.pci is None:
+ self._GetFreePCISlot(instance, device)
+ kvm_devid = _GenerateDeviceKVMId(dev_type, device)
+ runtime = self._LoadKVMRuntime(instance)
+ if dev_type == constants.HOTPLUG_TARGET_DISK:
+ command = "drive_add dummy file=%s,if=none,id=%s,format=raw\n" % \
+ (extra, kvm_devid)
+ command += ("device_add virtio-blk-pci,bus=pci.0,addr=%s,drive=%s,id=%s" %
+ (hex(device.pci), kvm_devid, kvm_devid))
+ elif dev_type == constants.HOTPLUG_TARGET_NIC:
+ (tap, fd) = _OpenTap()
+ self._ConfigureNIC(instance, seq, device, tap)
+ self._PassTapFd(instance, fd, device)
+ command = "netdev_add tap,id=%s,fd=%s\n" % (kvm_devid, kvm_devid)
+ args = "virtio-net-pci,bus=pci.0,addr=%s,mac=%s,netdev=%s,id=%s" % \
+ (hex(device.pci), device.mac, kvm_devid, kvm_devid)
+ command += "device_add %s" % args
+ utils.WriteFile(self._InstanceNICFile(instance.name, seq), data=tap)
+
+ self._CallHotplugCommand(instance.name, command)
+ # update relevant entries in runtime file
+ index = _DEVICE_RUNTIME_INDEX[dev_type]
+ entry = _RUNTIME_ENTRY[dev_type](device, extra)
+ runtime[index].append(entry)
+ self._SaveKVMRuntime(instance, runtime)
+
+ def HotDelDevice(self, instance, dev_type, device, _, seq):
+ """ Helper method for hot-del device
+
+ It gets device info from runtime file, generates the device name and
+ invokes the device specific method.
+
+ """
+ runtime = self._LoadKVMRuntime(instance)
+ entry = _GetExistingDeviceInfo(dev_type, device, runtime)
+ kvm_device = _RUNTIME_DEVICE[dev_type](entry)
+ kvm_devid = _GenerateDeviceKVMId(dev_type, kvm_device)
+ if dev_type == constants.HOTPLUG_TARGET_DISK:
+ command = "device_del %s" % kvm_devid
+ elif dev_type == constants.HOTPLUG_TARGET_NIC:
+ command = "device_del %s\n" % kvm_devid
+ command += "netdev_del %s" % kvm_devid
+ utils.RemoveFile(self._InstanceNICFile(instance.name, seq))
+ self._CallHotplugCommand(instance.name, command)
+ index = _DEVICE_RUNTIME_INDEX[dev_type]
+ runtime[index].remove(entry)
+ self._SaveKVMRuntime(instance, runtime)
+
+ return kvm_device.pci
+
+ def HotModDevice(self, instance, dev_type, device, _, seq):
+ """ Helper method for hot-mod device
+
+ It gets device info from runtime file, generates the device name and
+ invokes the device specific method. Currently only NICs support hot-mod
+
+ """
+ if dev_type == constants.HOTPLUG_TARGET_NIC:
+ # putting it back in the same pci slot
+ device.pci = self.HotDelDevice(instance, dev_type, device, _, seq)
+ # TODO: remove sleep when socat gets removed
+ time.sleep(2)
+ self.HotAddDevice(instance, dev_type, device, _, seq)
+
+ def _PassTapFd(self, instance, fd, nic):
+ """Pass file descriptor to kvm process via monitor socket using SCM_RIGHTS
+
+ """
+ # TODO: factor out code related to unix sockets.
+ # squash common parts between monitor and qmp
+ kvm_devid = _GenerateDeviceKVMId(constants.HOTPLUG_TARGET_NIC, nic)
+ command = "getfd %s\n" % kvm_devid
+ fds = [fd]
+ logging.info("%s", fds)
+ try:
+ monsock = MonitorSocket(self._InstanceMonitor(instance.name))
+ monsock.connect()
+ fdsend.sendfds(monsock.sock, command, fds=fds)
+ finally:
+ monsock.close()
+
@classmethod
def _ParseKVMVersion(cls, text):
"""Parse the KVM version from the --help output.