Francesco Romani has uploaded a new change for review. Change subject: virt: graphdev: support headless VM ......................................................................
virt: graphdev: support headless VM This patch add support for headless VMs, aka VMs without a graphic device. Noteworthy side effects of this patch: * It is now possible to create a VM without any display (aka headless VM), and they are supported. * The input 'display' parameter of the Vm.create API is no longer mandatory * In the API schema, the display* parameters are now marked as optional, even though Engine is expected to still send them in the near/medium term. * setTicket and the internally used _reviveTicket can now fail if they are invoked against a VM without graphic devices. Change-Id: Iafeb0bebfb43c089614127d94c054175c111ce54 Signed-off-by: Francesco Romani <from...@redhat.com> --- M tests/functional/virtTests.py M tests/vmTests.py M vdsm/API.py M vdsm/virt/vm.py M vdsm_api/vdsmapi-schema.json 5 files changed, 117 insertions(+), 72 deletions(-) git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/46/27846/1 diff --git a/tests/functional/virtTests.py b/tests/functional/virtTests.py index a074e62..9a25850 100644 --- a/tests/functional/virtTests.py +++ b/tests/functional/virtTests.py @@ -225,6 +225,14 @@ self._waitForStartup(vm, VM_MINIMAL_UPTIME) @requireKVM + def testHeadlessVm(self): + customization = {'vmId': '77777777-ffff-3333-bbbb-222222222222', + 'vmName': 'testHeadlessVm'} + + with RunningVm(self.vdsm, customization) as vm: + self._waitForStartup(vm, VM_MINIMAL_UPTIME) + + @requireKVM @permutations([['localfs'], ['iscsi'], ['nfs']]) def testVmWithStorage(self, backendType): disk = storage.StorageTest() diff --git a/tests/vmTests.py b/tests/vmTests.py index 69d5643..4022dfb 100644 --- a/tests/vmTests.py +++ b/tests/vmTests.py @@ -1077,6 +1077,11 @@ devs = fake.buildConfDevices() self.assertTrue(devs['graphics']) + def testGraphicDeviceHeadless(self): + with FakeVM(self.conf) as fake: + devs = fake.buildConfDevices() + self.assertFalse(devs['graphics']) + def testGraphicsDeviceMixed(self): """ if proper Graphics Devices are supplied, display* params must be diff --git a/vdsm/API.py b/vdsm/API.py index 14e7bae..4747be1 100644 --- a/vdsm/API.py +++ b/vdsm/API.py @@ -200,7 +200,7 @@ self.log.error("Error restoring VM parameters", exc_info=True) - requiredParams = ['vmId', 'memSize', 'display'] + requiredParams = ['vmId', 'memSize'] for param in requiredParams: if param not in vmParams: self.log.error('Missing required parameter %s' % (param)) @@ -252,11 +252,6 @@ 'No space on /tmp?'}} return errCode['createErr'] - if not vm.GraphicsDevice.isSupportedDisplayType(vmParams): - return {'status': {'code': errCode['createErr'] - ['status']['code'], - 'message': 'Unknown display type %s' % - vmParams.get('display')}} if 'nicModel' not in vmParams: vmParams['nicModel'] = config.get('vars', 'nic_model') return self._cif.createVm(vmParams) diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py index a547a5e..5d08cea 100644 --- a/vdsm/virt/vm.py +++ b/vdsm/virt/vm.py @@ -1344,10 +1344,6 @@ 'main', 'display', 'inputs', 'cursor', 'playback', 'record', 'smartcard', 'usbredir') - @staticmethod - def isSupportedDisplayType(vmParams): - return vmParams.get('display') in ('vnc', 'qxl', 'qxlnc') - def __init__(self, conf, log, **kwargs): super(GraphicsDevice, self).__init__(conf, log, **kwargs) self.port = self.LIBVIRT_PORT_AUTOSELECT @@ -1840,8 +1836,8 @@ if len(devices[device]) > 1: raise ValueError("only a single %s device is " "supported" % device) - if len(devices[GRAPHICS_DEVICES]) != 1: - raise ValueError("one graphics device is required") + if len(devices[GRAPHICS_DEVICES]) > 1: + raise ValueError("only one graphics device is supported") def getConfController(self): """ @@ -1866,12 +1862,16 @@ devType = DEFAULT_VIDEOS[self.arch] elif self.hasSpice: devType = 'qxl' + else: + devType = None - monitors = int(self.conf.get('spiceMonitors', '1')) - vram = '65536' if (monitors <= 2) else '32768' - for idx in range(monitors): - vcards.append({'type': VIDEO_DEVICES, 'specParams': {'vram': vram}, - 'device': devType}) + if devType: + monitors = int(self.conf.get('spiceMonitors', '1')) + vram = '65536' if (monitors <= 2) else '32768' + for idx in range(monitors): + vcards.append({'type': VIDEO_DEVICES, + 'specParams': {'vram': vram}, + 'device': devType}) return vcards @@ -1885,13 +1885,18 @@ for oldName, newName in GraphicsDevice.LEGACY_MAP.iteritems() if oldName in conf) - return [{ - 'type': GRAPHICS_DEVICES, - 'device': ( - 'spice' - if self.conf['display'] in ('qxl', 'qxlnc') - else 'vnc'), - 'specParams': makeSpecParams(self.conf)}] + graphDevs = [] + + if 'display' in self.conf: + graphDevs.append({ + 'type': GRAPHICS_DEVICES, + 'device': ( + 'spice' + if self.conf['display'] in ('qxl', 'qxlnc') + else 'vnc'), + 'specParams': makeSpecParams(self.conf)}) + + return graphDevs def getConfSound(self): """ @@ -2520,10 +2525,6 @@ return self._getExitedVmStats() stats = { - 'displayPort': self.conf['displayPort'], - 'displaySecurePort': self.conf['displaySecurePort'], - 'displayType': self.conf['display'], - 'displayIp': self.conf['displayIp'], 'pid': self.conf['pid'], 'vmType': self.conf['vmType'], 'kvmEnable': self._kvmEnable, @@ -2534,6 +2535,8 @@ stats['cdrom'] = self.conf['cdrom'] if 'boot' in self.conf: stats['boot'] = self.conf['boot'] + + stats.update(self._getGraphicsStats()) decStats = {} try: @@ -2602,6 +2605,16 @@ 'exitReason': self.conf['exitReason']} if 'timeOffset' in self.conf: stats['timeOffset'] = self.conf['timeOffset'] + return stats + + def _getGraphicsStats(self): + stats = {} + if 'display' in self.conf: + stats['displayType'] = self.conf['display'] + stats['displayPort'] = self.conf['displayPort'] + stats['displaySecurePort'] = self.conf['displaySecurePort'] + stats['displayIp'] = self.conf['displayIp'] + # else headless VM return stats def isMigrating(self): @@ -4266,8 +4279,13 @@ return {'status': doneCode, 'vmList': self.status()} def setTicket(self, otp, seconds, connAct, params): - graphics = _domParseStr(self._dom.XMLDesc(0)).childNodes[0]. \ - getElementsByTagName('graphics')[0] + try: + graphics = _domParseStr(self._dom.XMLDesc(0)).childNodes[0]. \ + getElementsByTagName('graphics')[0] + except IndexError: + return { + 'status': {'code': errCode['ticketErr']['status']['code'], + 'message': 'no graphics devices configured'}} graphics.setAttribute('passwd', otp) if int(seconds) > 0: validto = time.strftime('%Y-%m-%dT%H:%M:%S', @@ -4288,9 +4306,13 @@ def _reviveTicket(self, newlife): """Revive an existing ticket, if it has expired or about to expire""" - graphics = _domParseStr( - self._dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)). \ - childNodes[0].getElementsByTagName('graphics')[0] + try: + graphics = _domParseStr( + self._dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)). \ + childNodes[0].getElementsByTagName('graphics')[0] + except IndexError: + self.log.error('no graphics devices configured') + return validto = max(time.strptime(graphics.getAttribute('passwdValidTo'), '%Y-%m-%dT%H:%M:%S'), time.gmtime(time.time() + newlife)) @@ -4855,19 +4877,20 @@ device of each type (sdl, vnc, spice) is supported """ graphicsXml = _domParseStr(self._lastXMLDesc).childNodes[0]. \ - getElementsByTagName('devices')[0].getElementsByTagName('graphics')[0] + getElementsByTagName('devices')[0].getElementsByTagName('graphics') - graphicsType = graphicsXml.getAttribute('type') - for dev in self.conf['devices']: - if dev.get('device') == graphicsType: - port = graphicsXml.getAttribute('port') - if port: - dev['port'] = port - tlsPort = graphicsXml.getAttribute('tlsPort') - if tlsPort: - dev['tlsPort'] = tlsPort - self._updateLegacyConf(dev) - break + for gxml in graphicsXml: + graphicsType = gxml.getAttribute('type') + + for dev in self.conf['devices']: + if dev.get('device') == graphicsType: + port = gxml.getAttribute('port') + if port: + dev['port'] = port + tlsPort = gxml.getAttribute('tlsPort') + if tlsPort: + dev['tlsPort'] = tlsPort + self._updateLegacyConf(dev) def _getUnderlyingNetworkInterfaceInfo(self): """ @@ -5051,11 +5074,12 @@ return True def _initLegacyConf(self): - self.conf['displayPort'] = GraphicsDevice.LIBVIRT_PORT_AUTOSELECT - self.conf['displaySecurePort'] = \ - GraphicsDevice.LIBVIRT_PORT_AUTOSELECT - self.conf['displayIp'] = _getNetworkIp( - self.conf.get('displayNetwork')) + if 'display' in self.conf: + self.conf['displayPort'] = GraphicsDevice.LIBVIRT_PORT_AUTOSELECT + self.conf['displaySecurePort'] = \ + GraphicsDevice.LIBVIRT_PORT_AUTOSELECT + self.conf['displayIp'] = _getNetworkIp( + self.conf.get('displayNetwork')) def _updateLegacyConf(self, dev): self.conf['display'] = 'qxl' if dev['device'] == 'spice' else 'vnc' diff --git a/vdsm_api/vdsmapi-schema.json b/vdsm_api/vdsmapi-schema.json index a9332ee..64d4179 100644 --- a/vdsm_api/vdsmapi-schema.json +++ b/vdsm_api/vdsmapi-schema.json @@ -3175,13 +3175,17 @@ # # @devices: #optional An array of VM devices present # -# @display: The type of display +# @display: #optional The type of display +# (made optional in version 4.15.0) # -# @displayIp: The IP address to use for accessing the VM display +# @displayIp: #optional The IP address to use for accessing the VM display +# (made optional in version 4.15.0) # -# @displayPort: The port in use for unencrypted display data +# @displayPort: #optional The port in use for unencrypted display data +# (made optional in version 4.15.0) # -# @displaySecurePort: The port in use for encrypted display data +# @displaySecurePort: #optional The port in use for encrypted display data +# (made optional in version 4.15.0) # # @emulatedMachine: #optional The machine specification being emulated # @@ -3235,8 +3239,8 @@ {'type': 'VmDefinition', 'data': {'acpiEnable': 'bool', 'clientIp': 'str', 'cpuShares': 'str', '*cpuType': 'str', '*custom': 'StringMap', '*devices': ['VmDevice'], - 'display': 'VmDisplayType', 'displayIp': 'str', - 'displayPort': 'int', 'displaySecurePort': 'int', + '*display': 'VmDisplayType', '*displayIp': 'str', + '*displayPort': 'int', '*displaySecurePort': 'int', '*emulatedMachine': 'str', '*keyboardLayout': 'str', 'kvmEnable': 'bool', '*maxVCpus': 'uint', 'memSize': 'uint', 'memGuaranteedSize': 'uint', 'nicModel': 'str', 'nice': 'int', @@ -3264,7 +3268,8 @@ # # @devices: #optional An array of VM devices requested # -# @display: The type of display +# @display: #optional The type of display +# (made optional in version 4.15.0) # # @kvmEnable: Indicates if KVM hardware acceleration is enabled # @@ -3300,7 +3305,7 @@ {'type': 'VmParameters', 'data': {'acpiEnable': 'bool', '*bootMenuEnable': 'bool', '*cpuShares': 'str', '*custom': 'StringMap', '*devices': ['VmDevice'], - 'display': 'VmDisplayType', 'kvmEnable': 'bool', 'memSize': 'uint', + '*display': 'VmDisplayType', 'kvmEnable': 'bool', 'memSize': 'uint', 'nice': 'int', 'smp': 'uint', '*smpCoresPerSocket': 'uint', '*smpThreadsPerCore': 'uint', 'timeOffset': 'uint', 'transparentHugePages': 'bool', 'vmId': 'UUID', 'vmName': 'str', @@ -3347,7 +3352,8 @@ # # @devices: An array of VM devices requested # -# @display: The type of display +# @display: #optional The type of display +# (made optional in version 4.15.0) # # @kvmEnable: Indicates if KVM hardware acceleration is enabled # @@ -3372,7 +3378,8 @@ # # @memGuaranteedSize: The amount of memory guaranteed to the VM in MB # -# @displaySecurePort: The port in use for encrypted display data +# @displaySecurePort: #optional The port in use for encrypted display data +# (made optional in version 4.15.0) # # @spiceSecureChannels: Secure space channels comma separated # @@ -3394,11 +3401,13 @@ # @guestFQDN: Fully qualified domain name of the guest OS. (Reported # by the guest agent) # -# @displayIp: The IP address to use for accessing the VM display +# @displayIp: #optional The IP address to use for accessing the VM display +# (made optional in version 4.15.0) # # @keyboardLayout: The keyboard layout string (eg. 'en-us') # -# @displayPort: The port in use for unencrypted display data +# @displayPort: #optional The port in use for unencrypted display data +# (made optional in version 4.15.0) # # @guestIPs: A space separated string of assigned IPv4 addresses # @@ -3416,15 +3425,15 @@ ## {'type': 'VMFullInfo', 'data': {'acpiEnable': 'bool', 'custom': 'StringMap', 'devices': ['VmDevice'], - 'display': 'VmDisplayType', 'kvmEnable': 'bool', 'memSize': 'uint', + '*display': 'VmDisplayType', 'kvmEnable': 'bool', 'memSize': 'uint', 'nice': 'int', 'smp': 'uint', 'smpCoresPerSocket': 'uint', 'timeOffset': 'uint', 'transparentHugePages': 'bool', 'vmId': 'UUID', 'vmName': 'str', 'vmType': 'VmType', 'memGuaranteedSize': 'uint', - 'displaySecurePort': 'uint', 'spiceSecureChannels': 'str', + '*displaySecurePort': 'uint', 'spiceSecureChannels': 'str', 'username': 'str', 'emulatedMachine': 'str', 'pid': 'uint', 'spiceSslCipherSuite': 'str', 'cpuType': 'str', 'pauseCode': 'str', - 'guestFQDN': 'str', 'displayIp': 'str', 'keyboardLayout': 'str', - 'displayPort': 'uint', 'guestIPs': 'str', 'smartcardEnable': 'bool', + 'guestFQDN': 'str', '*displayIp': 'str', 'keyboardLayout': 'str', + '*displayPort': 'uint', 'guestIPs': 'str', 'smartcardEnable': 'bool', 'nicModel': 'VmInterfaceDeviceModel', 'pitReinjection': 'bool', 'status': 'str', 'clientIp': 'str'}} @@ -6016,13 +6025,17 @@ # # Statistics for a running virtual machine. # -# @displayPort: The port in use for unencrypted display data +# @displayPort: #optional The port in use for unencrypted display data +# (made optional in version 4.15.0) # -# @displaySecurePort: The port in use for encrypted display data +# @displaySecurePort: #optional The port in use for encrypted display data +# (made optional in version 4.15.0) # -# @displayType: The type of display in use +# @displayType: #optional The type of display in use +# (made optional in version 4.15.0) # -# @displayIp: The IP address to use for accessing the VM display +# @displayIp: #optional The IP address to use for accessing the VM display +# (made optional in version 4.15.0) # # @pid: The process ID of the underlying qemu process # @@ -6089,8 +6102,8 @@ # Since: 4.10.0 ## {'type': 'RunningVmStats', - 'data': {'displayPort': 'uint', 'displaySecurePort': 'uint', - 'displayType': 'VmDisplayType', 'displayIp': 'str', 'pid': 'uint', + 'data': {'*displayPort': 'uint', '*displaySecurePort': 'uint', + '*displayType': 'VmDisplayType', '*displayIp': 'str', 'pid': 'uint', 'vmType': 'VmType', 'kvmEnable': 'bool', 'network': 'NetworkInterfaceStatsMap', 'disks': 'VmDiskStatsMap', 'monitorResponse': 'int', -- To view, visit http://gerrit.ovirt.org/27846 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iafeb0bebfb43c089614127d94c054175c111ce54 Gerrit-PatchSet: 1 Gerrit-Project: vdsm Gerrit-Branch: master Gerrit-Owner: Francesco Romani <from...@redhat.com> _______________________________________________ vdsm-patches mailing list vdsm-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches