Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nova/conf/libvirt.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"""),
cfg.StrOpt('virt_type',
default='kvm',
choices=('kvm', 'lxc', 'qemu', 'parallels'),
choices=('kvm', 'lxc', 'qemu', 'parallels', 'ch'),
help="""
Describes the virtualization type (or so called domain type) libvirt should
use.
Expand Down
63 changes: 50 additions & 13 deletions nova/privsep/libvirt.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,25 +146,62 @@ def unplug_plumgrid_vif(dev):
processutils.execute('ifc_ctl', 'gateway', 'ifdown', dev)
processutils.execute('ifc_ctl', 'gateway', 'del_port', dev)

def readpty_once(path):
import select

data = bytearray()
epoll = select.epoll()

with open(path, "w") as f:
# Writing a null seems to trigger new output while not being
# recognized as a newline or similar by the sender.
f.write("\0")
f.flush()

with open(path, "rb") as f:
os.set_blocking(f.fileno(), False)
epoll.register(f.fileno(), select.EPOLLIN)
poll_list = epoll.poll(0.1)
for _ in poll_list:
data += f.read()
epoll.unregister(f.fileno())
epoll.close()

return data.decode("utf-8", errors="ignore")


@nova.privsep.sys_admin_pctxt.entrypoint
def readpty(path):
# TODO(mikal): I'm not a huge fan that we don't enforce a valid pty path
# here, but I haven't come up with a great way of doing that.
"""
The pty created by Cloud Hypervisor is a bit tricky. It seems to require
some kind of input to deliver output again. Therefore, we send in a '\0'
byte before reading (the null byte does not seem to trigger any
interactions with the pty like a newline would do).
The reading is done in a loop and we cancel reading when no data is
returned anymore. Doing so has proven to deliver all the output available
in the pty.
"""
# We track how many times we read zero bytes form the pty
consecutive_zero = 0
data = ""
try:
# We finish reading once we read zero multiple times continuously. The range
# is just a safeguard to finish reading if the terminal happens to have new
# data all the time.
for _ in range(1000):
out = readpty_once(path)

# NOTE(mikal): I am deliberately not catching the ImportError
# exception here... Some platforms (I'm looking at you Windows)
# don't have a fcntl and we may as well let them know that
# with an ImportError, not that they should be calling this at all.
import fcntl
if len(out) == 0:
consecutive_zero += 1
else:
consecutive_zero = 0

try:
with open(path, 'r') as f:
current_flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFL)
fcntl.fcntl(f.fileno(), fcntl.F_SETFL,
current_flags | os.O_NONBLOCK)
data += out

if consecutive_zero > 10:
return data

return f.read()
return data

except Exception as exc:
# NOTE(mikal): dear internet, I see you looking at me with your
Expand Down
3 changes: 3 additions & 0 deletions nova/virt/libvirt/blockinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
'qemu': ['virtio', 'scsi', 'ide', 'usb', 'fdc', 'sata'],
'kvm': ['virtio', 'scsi', 'ide', 'usb', 'fdc', 'sata'],
'lxc': ['lxc'],
'ch': ['virtio'],
'parallels': ['ide', 'scsi'],
# we no longer support UML or Xen, but we keep track of their bus types so
# we can reject them for other virt types
Expand Down Expand Up @@ -276,6 +277,8 @@ def get_disk_bus_for_device_type(instance,
return "ide"
elif device_type == "disk":
return "scsi"
elif virt_type == "ch":
return "virtio"
else:
# If virt-type not in list then it is unsupported
raise exception.UnsupportedVirtType(virt=virt_type)
Expand Down
27 changes: 24 additions & 3 deletions nova/virt/libvirt/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@ def _check_cpu_compatibility(self):
mode = CONF.libvirt.cpu_mode
models = CONF.libvirt.cpu_models

if (CONF.libvirt.virt_type not in ("kvm", "qemu") and
if (CONF.libvirt.virt_type not in ("kvm", "qemu", "ch") and
mode not in (None, 'none')):
msg = _("Config requested an explicit CPU model, but "
"the current libvirt hypervisor '%s' does not "
Expand Down Expand Up @@ -1379,6 +1379,8 @@ def _uri():
uri = CONF.libvirt.connection_uri or 'lxc:///'
elif CONF.libvirt.virt_type == 'parallels':
uri = CONF.libvirt.connection_uri or 'parallels:///system'
elif CONF.libvirt.virt_type == 'ch':
uri = CONF.libvirt.connection_uri or 'ch:///system'
else:
uri = CONF.libvirt.connection_uri or 'qemu:///system'
return uri
Expand All @@ -1389,6 +1391,7 @@ def _live_migration_uri(dest):
'kvm': 'qemu+%(scheme)s://%(dest)s/system',
'qemu': 'qemu+%(scheme)s://%(dest)s/system',
'parallels': 'parallels+tcp://%(dest)s/system',
'ch': 'ch+%(scheme)s://%(dest)s/system',
}
dest = oslo_netutils.escape_ipv6(dest)

Expand Down Expand Up @@ -4494,7 +4497,8 @@ def get_console_output(self, context, instance):
path_sources = [
('file', "./devices/console[@type='file']/source[@path]", 'path'),
('tcp', "./devices/console[@type='tcp']/log[@file]", 'file'),
('pty', "./devices/console[@type='pty']/source[@path]", 'path')]
('pty', "./devices/console[@type='pty']/source[@path]", 'path'),
('pty', "./devices/serial[@type='pty']/source[@path]", 'path'), ]
console_type = ""
console_path = ""
for c_type, epath, attrib in path_sources:
Expand Down Expand Up @@ -5405,6 +5409,10 @@ def _get_guest_cpu_model_config(self, flavor=None, arch=None):
if not models:
models = ['max']

elif CONF.libvirt.virt_type == "ch":
# Currently, we only support host-passthrough with Cloud Hypervisor. This
# changes as soon as we support proper CPU profiles.
mode = "host-passthrough"
else:
if mode is None or mode == "none":
return None
Expand Down Expand Up @@ -6735,6 +6743,9 @@ def _configure_guest_by_virt_type(
guest.os_init_path = "/sbin/init"
guest.os_cmdline = CONSOLE
guest.os_init_env["product_name"] = "OpenStack Nova"
elif CONF.libvirt.virt_type == "ch":
guest.virt_type = 'kvm'
guest.os_kernel = "/usr/share/cloud-hypervisor/CLOUDHV_EFI.fd"
elif CONF.libvirt.virt_type == "parallels":
if guest.os_type == fields.VMMode.EXE:
guest.os_init_path = "/sbin/init"
Expand Down Expand Up @@ -6780,6 +6791,11 @@ def _create_consoles(self, guest_cfg, instance, flavor, image_meta):
self._create_pty_device(
guest_cfg, vconfig.LibvirtConfigGuestConsole,
log_path=log_path)
elif CONF.libvirt.virt_type == "ch":
consolepty = vconfig.LibvirtConfigGuestSerial()
consolepty.type = "pty"
guest_cfg.add_device(consolepty)

else: # qemu, kvm
if self._is_s390x_guest(image_meta):
self._create_consoles_s390x(
Expand Down Expand Up @@ -6969,6 +6985,8 @@ def _guest_add_usb_root_controller(self, guest, image_meta):
here explicitly so that we can _disable_ it (by setting the model to
'none') if it's not necessary.
"""
if CONF.libvirt.virt_type == "ch":
return
usbhost = vconfig.LibvirtConfigGuestUSBHostController()
usbhost.index = 0
# an unset model means autodetect, while 'none' means don't add a
Expand Down Expand Up @@ -7378,6 +7396,8 @@ def _guest_add_accel_pci_devices(self, guest, accel_info):
def _guest_add_video_device(guest):
if CONF.libvirt.virt_type == 'lxc':
return False
elif CONF.libvirt.virt_type == "ch":
return False

# NB some versions of libvirt support both SPICE and VNC
# at the same time. We're not trying to second guess which
Expand Down Expand Up @@ -8678,7 +8698,8 @@ def _has_numa_support(self):
if (caps.host.cpu.arch in (fields.Architecture.I686,
fields.Architecture.X86_64,
fields.Architecture.AARCH64) and
self._host.has_min_version(hv_type=host.HV_DRIVER_QEMU)):
(self._host.has_min_version(hv_type=host.HV_DRIVER_QEMU) or
self._host.has_min_version(hv_type=host.HV_DRIVER_CH))):
return True
elif (caps.host.cpu.arch in (fields.Architecture.PPC64,
fields.Architecture.PPC64LE)):
Expand Down
2 changes: 2 additions & 0 deletions nova/virt/libvirt/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
# This list is for libvirt hypervisor drivers that need special handling.
# This is *not* the complete list of supported hypervisor drivers.
HV_DRIVER_QEMU = "QEMU"
HV_DRIVER_CH = "CH"

SEV_KERNEL_PARAM_FILE = '/sys/module/kvm_amd/parameters/sev'

Expand Down Expand Up @@ -1241,6 +1242,7 @@ def write_instance_config(self, xml):

:returns: an instance of Guest
"""
LOG.info(xml)
domain = self.get_connection().defineXML(xml)
return libvirt_guest.Guest(domain)

Expand Down
5 changes: 4 additions & 1 deletion nova/virt/libvirt/vif.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
network_model.VIF_MODEL_RTL8139,
network_model.VIF_MODEL_E1000,
],
'ch': [
network_model.VIF_MODEL_VIRTIO,
],
}


Expand Down Expand Up @@ -172,7 +175,7 @@ def get_vif_model(self, image_meta=None, vif_model=None):
# If the virt type is KVM/QEMU/VZ(Parallels), then use virtio according
# to the global config parameter
if (model is None and CONF.libvirt.virt_type in
('kvm', 'qemu', 'parallels') and
('kvm', 'qemu', 'parallels', 'ch') and
CONF.libvirt.use_virtio_for_bridges):
model = network_model.VIF_MODEL_VIRTIO

Expand Down