本文首發於泊浮目的專欄: https://segmentfault.com/blog...
在上篇文章中(ZStack源碼剖析之二次開發——可擴展框架
),咱們簡單的瞭解了一下ZStack核心引擎的二次開發技巧。在這篇文章中,咱們將一塊兒來了解ZStack-Utility
(即ZStack的Agent端)的二開姿式。python
咱們以ZStack管理節點調用startVm這個api爲例子,一塊兒來看一下在agent上的執行邏輯。linux
def start(self): http_server = kvmagent.get_http_server() http_server.register_async_uri(self.KVM_START_VM_PATH, self.start_vm)
首先,得註冊一個http path用來接受reqeust。git
@kvmagent.replyerror def start_vm(self, req): cmd = jsonobject.loads(req[http.REQUEST_BODY]) rsp = StartVmResponse() try: self._record_operation(cmd.vmInstanceUuid, self.VM_OP_START) self._start_vm(cmd) logger.debug('successfully started vm[uuid:%s, name:%s]' % (cmd.vmInstanceUuid, cmd.vmName)) except kvmagent.KvmError as e: e_str = linux.get_exception_stacktrace() logger.warn(e_str) if "burst" in e_str and "Illegal" in e_str and "rate" in e_str: rsp.error = "QoS exceed max limit, please check and reset it in zstack" elif "cannot set up guest memory" in e_str: logger.warn('unable to start vm[uuid:%s], %s' % (cmd.vmInstanceUuid, e_str)) rsp.error = "No enough physical memory for guest" else: rsp.error = e_str err = self.handle_vfio_irq_conflict(cmd.vmInstanceUuid) if err != "": rsp.error = "%s, details: %s" % (err, rsp.error) rsp.success = False return jsonobject.dumps(rsp)
直接進入主幹邏輯,self._start_vm(cmd)
。github
@lock.lock('libvirt-startvm') def _start_vm(self, cmd): try: vm = get_vm_by_uuid_no_retry(cmd.vmInstanceUuid, False) if vm: if vm.state == Vm.VM_STATE_RUNNING: raise kvmagent.KvmError( 'vm[uuid:%s, name:%s] is already running' % (cmd.vmInstanceUuid, vm.get_name())) else: vm.destroy() vm = Vm.from_StartVmCmd(cmd) vm.start(cmd.timeout) except libvirt.libvirtError as e: logger.warn(linux.get_exception_stacktrace()) if "Device or resource busy" in str(e.message): raise kvmagent.KvmError( 'unable to start vm[uuid:%s, name:%s], libvirt error: %s' % ( cmd.vmInstanceUuid, cmd.vmName, str(e))) try: vm = get_vm_by_uuid(cmd.vmInstanceUuid) if vm and vm.state != Vm.VM_STATE_RUNNING: raise kvmagent.KvmError( 'vm[uuid:%s, name:%s, state:%s] is not in running state, libvirt error: %s' % ( cmd.vmInstanceUuid, cmd.vmName, vm.state, str(e))) except kvmagent.KvmError: raise kvmagent.KvmError( 'unable to start vm[uuid:%s, name:%s], libvirt error: %s' % (cmd.vmInstanceUuid, cmd.vmName, str(e)))
關鍵邏輯:shell
vm = Vm.from_StartVmCmd(cmd) vm.start(cmd.timeout)
先看from_StartVmCmd
json
@staticmethod def from_StartVmCmd(cmd): use_virtio = cmd.useVirtio use_numa = cmd.useNuma elements = {} def make_root(): root = etree.Element('domain') root.set('type', 'kvm') # self._root.set('type', 'qemu') root.set('xmlns:qemu', 'http://libvirt.org/schemas/domain/qemu/1.0') elements['root'] = root def make_cpu(): if use_numa: root = elements['root'] e(root, 'vcpu', '128', {'placement': 'static', 'current': str(cmd.cpuNum)}) # e(root,'vcpu',str(cmd.cpuNum),{'placement':'static'}) tune = e(root, 'cputune') e(tune, 'shares', str(cmd.cpuSpeed * cmd.cpuNum)) # enable nested virtualization if cmd.nestedVirtualization == 'host-model': cpu = e(root, 'cpu', attrib={'mode': 'host-model'}) e(cpu, 'model', attrib={'fallback': 'allow'}) elif cmd.nestedVirtualization == 'host-passthrough': cpu = e(root, 'cpu', attrib={'mode': 'host-passthrough'}) e(cpu, 'model', attrib={'fallback': 'allow'}) elif IS_AARCH64: cpu = e(root, 'cpu', attrib={'mode': 'host-passthrough'}) e(cpu, 'model', attrib={'fallback': 'allow'}) else: cpu = e(root, 'cpu') # e(cpu, 'topology', attrib={'sockets': str(cmd.socketNum), 'cores': str(cmd.cpuOnSocket), 'threads': '1'}) mem = cmd.memory / 1024 e(cpu, 'topology', attrib={'sockets': str(32), 'cores': str(4), 'threads': '1'}) numa = e(cpu, 'numa') e(numa, 'cell', attrib={'id': '0', 'cpus': '0-127', 'memory': str(mem), 'unit': 'KiB'}) else: root = elements['root'] # e(root, 'vcpu', '128', {'placement': 'static', 'current': str(cmd.cpuNum)}) e(root, 'vcpu', str(cmd.cpuNum), {'placement': 'static'}) tune = e(root, 'cputune') e(tune, 'shares', str(cmd.cpuSpeed * cmd.cpuNum)) # enable nested virtualization if cmd.nestedVirtualization == 'host-model': cpu = e(root, 'cpu', attrib={'mode': 'host-model'}) e(cpu, 'model', attrib={'fallback': 'allow'}) elif cmd.nestedVirtualization == 'host-passthrough': cpu = e(root, 'cpu', attrib={'mode': 'host-passthrough'}) e(cpu, 'model', attrib={'fallback': 'allow'}) elif IS_AARCH64: cpu = e(root, 'cpu', attrib={'mode': 'host-passthrough'}) e(cpu, 'model', attrib={'fallback': 'allow'}) else: cpu = e(root, 'cpu') e(cpu, 'topology', attrib={'sockets': str(cmd.socketNum), 'cores': str(cmd.cpuOnSocket), 'threads': '1'}) def make_memory(): root = elements['root'] mem = cmd.memory / 1024 if use_numa: e(root, 'maxMemory', str(68719476736), {'slots': str(16), 'unit': 'KiB'}) # e(root,'memory',str(mem),{'unit':'k'}) e(root, 'currentMemory', str(mem), {'unit': 'k'}) else: e(root, 'memory', str(mem), {'unit': 'k'}) e(root, 'currentMemory', str(mem), {'unit': 'k'}) def make_os(): root = elements['root'] os = e(root, 'os') if IS_AARCH64: e(os, 'type', 'hvm', attrib={'arch': 'aarch64'}) e(os, 'loader', '/usr/share/edk2.git/aarch64/QEMU_EFI-pflash.raw', attrib={'readonly': 'yes', 'type': 'pflash'}) else: e(os, 'type', 'hvm', attrib={'machine': 'pc'}) # if not booting from cdrom, don't add any boot element in os section if cmd.bootDev[0] == "cdrom": for boot_dev in cmd.bootDev: e(os, 'boot', None, {'dev': boot_dev}) if cmd.useBootMenu: e(os, 'bootmenu', attrib={'enable': 'yes'}) def make_features(): root = elements['root'] features = e(root, 'features') for f in ['acpi', 'apic', 'pae']: e(features, f) if cmd.kvmHiddenState == True: kvm = e(features, "kvm") e(kvm, 'hidden', None, {'state': 'on'}) def make_devices(): root = elements['root'] devices = e(root, 'devices') if cmd.addons and cmd.addons['qemuPath']: e(devices, 'emulator', cmd.addons['qemuPath']) else: e(devices, 'emulator', kvmagent.get_qemu_path()) tablet = e(devices, 'input', None, {'type': 'tablet', 'bus': 'usb'}) e(tablet, 'address', None, {'type':'usb', 'bus':'0', 'port':'1'}) if IS_AARCH64: keyboard = e(devices, 'input', None, {'type': 'keyboard', 'bus': 'usb'}) elements['devices'] = devices def make_cdrom(): devices = elements['devices'] MAX_CDROM_NUM = len(Vm.ISO_DEVICE_LETTERS) EMPTY_CDROM_CONFIGS = None if IS_AARCH64: # AArch64 Does not support the attachment of multiple iso EMPTY_CDROM_CONFIGS = [ EmptyCdromConfig(None, None, None) ] else: # bus 0 unit 0 already use by root volume EMPTY_CDROM_CONFIGS = [ EmptyCdromConfig('hd%s' % Vm.ISO_DEVICE_LETTERS[0], '0', '1'), EmptyCdromConfig('hd%s' % Vm.ISO_DEVICE_LETTERS[1], '1', '0'), EmptyCdromConfig('hd%s' % Vm.ISO_DEVICE_LETTERS[2], '1', '1') ] if len(EMPTY_CDROM_CONFIGS) != MAX_CDROM_NUM: logger.error('ISO_DEVICE_LETTERS or EMPTY_CDROM_CONFIGS config error') def makeEmptyCdrom(targetDev, bus, unit): cdrom = e(devices, 'disk', None, {'type': 'file', 'device': 'cdrom'}) e(cdrom, 'driver', None, {'name': 'qemu', 'type': 'raw'}) if IS_AARCH64: e(cdrom, 'target', None, {'dev': 'sdc', 'bus': 'scsi'}) else: e(cdrom, 'target', None, {'dev': targetDev, 'bus': 'ide'}) e(cdrom, 'address', None,{'type' : 'drive', 'bus' : bus, 'unit' : unit}) e(cdrom, 'readonly', None) return cdrom if not cmd.bootIso: for config in EMPTY_CDROM_CONFIGS: makeEmptyCdrom(config.targetDev, config.bus, config.unit) return notEmptyCdrom = set([]) for iso in cmd.bootIso: notEmptyCdrom.add(iso.deviceId) cdromConfig = EMPTY_CDROM_CONFIGS[iso.deviceId] if iso.path.startswith('ceph'): ic = IsoCeph() ic.iso = iso devices.append(ic.to_xmlobject(cdromConfig.targetDev, cdromConfig.bus , cdromConfig.unit)) elif iso.path.startswith('fusionstor'): ic = IsoFusionstor() ic.iso = iso devices.append(ic.to_xmlobject(cdromConfig.targetDev, cdromConfig.bus , cdromConfig.unit)) else: cdrom = makeEmptyCdrom(cdromConfig.targetDev, cdromConfig.bus , cdromConfig.unit) e(cdrom, 'source', None, {'file': iso.path}) emptyCdrom = set(range(MAX_CDROM_NUM)).difference(notEmptyCdrom) for i in emptyCdrom: cdromConfig = EMPTY_CDROM_CONFIGS[i] makeEmptyCdrom(cdromConfig.targetDev, cdromConfig.bus, cdromConfig.unit) def make_volumes(): devices = elements['devices'] volumes = [cmd.rootVolume] volumes.extend(cmd.dataVolumes) def filebased_volume(_dev_letter, _v): disk = etree.Element('disk', {'type': 'file', 'device': 'disk', 'snapshot': 'external'}) e(disk, 'driver', None, {'name': 'qemu', 'type': linux.get_img_fmt(_v.installPath), 'cache': _v.cacheMode}) e(disk, 'source', None, {'file': _v.installPath}) if _v.shareable: e(disk, 'shareable') if _v.useVirtioSCSI: e(disk, 'target', None, {'dev': 'sd%s' % _dev_letter, 'bus': 'scsi'}) e(disk, 'wwn', _v.wwn) e(disk, 'address', None, {'type': 'drive', 'controller': '0', 'unit': str(_v.deviceId)}) return disk if _v.useVirtio: e(disk, 'target', None, {'dev': 'vd%s' % _dev_letter, 'bus': 'virtio'}) elif IS_AARCH64: e(disk, 'target', None, {'dev': 'sd%s' % _dev_letter, 'bus': 'scsi'}) else: e(disk, 'target', None, {'dev': 'sd%s' % _dev_letter, 'bus': 'ide'}) return disk def iscsibased_volume(_dev_letter, _v): def blk_iscsi(): bi = BlkIscsi() portal, bi.target, bi.lun = _v.installPath.lstrip('iscsi://').split('/') bi.server_hostname, bi.server_port = portal.split(':') bi.device_letter = _dev_letter bi.volume_uuid = _v.volumeUuid bi.chap_username = _v.chapUsername bi.chap_password = _v.chapPassword return bi.to_xmlobject() def virtio_iscsi(): vi = VirtioIscsi() portal, vi.target, vi.lun = _v.installPath.lstrip('iscsi://').split('/') vi.server_hostname, vi.server_port = portal.split(':') vi.device_letter = _dev_letter vi.volume_uuid = _v.volumeUuid vi.chap_username = _v.chapUsername vi.chap_password = _v.chapPassword return vi.to_xmlobject() if _v.useVirtio: return virtio_iscsi() else: return blk_iscsi() def ceph_volume(_dev_letter, _v): def ceph_virtio(): vc = VirtioCeph() vc.volume = _v vc.dev_letter = _dev_letter return vc.to_xmlobject() def ceph_blk(): if not IS_AARCH64: ic = IdeCeph() else: ic = ScsiCeph() ic.volume = _v ic.dev_letter = _dev_letter return ic.to_xmlobject() def ceph_virtio_scsi(): vsc = VirtioSCSICeph() vsc.volume = _v vsc.dev_letter = _dev_letter return vsc.to_xmlobject() if _v.useVirtioSCSI: disk = ceph_virtio_scsi() if _v.shareable: e(disk, 'shareable') return disk if _v.useVirtio: return ceph_virtio() else: return ceph_blk() def fusionstor_volume(_dev_letter, _v): def fusionstor_virtio(): vc = VirtioFusionstor() vc.volume = _v vc.dev_letter = _dev_letter return vc.to_xmlobject() def fusionstor_blk(): ic = IdeFusionstor() ic.volume = _v ic.dev_letter = _dev_letter return ic.to_xmlobject() def fusionstor_virtio_scsi(): vsc = VirtioSCSIFusionstor() vsc.volume = _v vsc.dev_letter = _dev_letter return vsc.to_xmlobject() if _v.useVirtioSCSI: disk = fusionstor_virtio_scsi() if _v.shareable: e(disk, 'shareable') return disk if _v.useVirtio: return fusionstor_virtio() else: return fusionstor_blk() def volume_qos(volume_xml_obj): if not cmd.addons: return vol_qos = cmd.addons['VolumeQos'] if not vol_qos: return qos = vol_qos[v.volumeUuid] if not qos: return if not qos.totalBandwidth and not qos.totalIops: return iotune = e(volume_xml_obj, 'iotune') if qos.totalBandwidth: e(iotune, 'total_bytes_sec', str(qos.totalBandwidth)) if qos.totalIops: # e(iotune, 'total_iops_sec', str(qos.totalIops)) e(iotune, 'read_iops_sec', str(qos.totalIops)) e(iotune, 'write_iops_sec', str(qos.totalIops)) # e(iotune, 'read_iops_sec_max', str(qos.totalIops)) # e(iotune, 'write_iops_sec_max', str(qos.totalIops)) # e(iotune, 'total_iops_sec_max', str(qos.totalIops)) volumes.sort(key=lambda d: d.deviceId) scsi_device_ids = [v.deviceId for v in volumes if v.useVirtioSCSI] for v in volumes: if v.deviceId >= len(Vm.DEVICE_LETTERS): err = "exceeds max disk limit, it's %s but only 26 allowed" % v.deviceId logger.warn(err) raise kvmagent.KvmError(err) dev_letter = Vm.DEVICE_LETTERS[v.deviceId] if v.useVirtioSCSI: dev_letter = Vm.DEVICE_LETTERS[scsi_device_ids.pop()] if v.deviceType == 'file': vol = filebased_volume(dev_letter, v) elif v.deviceType == 'iscsi': vol = iscsibased_volume(dev_letter, v) elif v.deviceType == 'ceph': vol = ceph_volume(dev_letter, v) elif v.deviceType == 'fusionstor': vol = fusionstor_volume(dev_letter, v) else: raise Exception('unknown volume deviceType: %s' % v.deviceType) assert vol is not None, 'vol cannot be None' # set boot order for root volume when boot from hd if v.deviceId == 0 and cmd.bootDev[0] == 'hd' and cmd.useBootMenu: e(vol, 'boot', None, {'order': '1'}) volume_qos(vol) devices.append(vol) def make_nics(): if not cmd.nics: return def nic_qos(nic_xml_object): if not cmd.addons: return nqos = cmd.addons['NicQos'] if not nqos: return qos = nqos[nic.uuid] if not qos: return if not qos.outboundBandwidth and not qos.inboundBandwidth: return bandwidth = e(nic_xml_object, 'bandwidth') if qos.outboundBandwidth: e(bandwidth, 'outbound', None, {'average': str(qos.outboundBandwidth / 1024 / 8)}) if qos.inboundBandwidth: e(bandwidth, 'inbound', None, {'average': str(qos.inboundBandwidth / 1024 / 8)}) devices = elements['devices'] for nic in cmd.nics: interface = e(devices, 'interface', None, {'type': 'bridge'}) e(interface, 'mac', None, {'address': nic.mac}) if nic.ip is not None and nic.ip != "": filterref = e(interface, 'filterref', None, {'filter':'clean-traffic'}) e(filterref, 'parameter', None, {'name':'IP', 'value': nic.ip}) e(interface, 'alias', None, {'name': 'net%s' % nic.nicInternalName.split('.')[1]}) e(interface, 'source', None, {'bridge': nic.bridgeName}) if use_virtio: e(interface, 'model', None, {'type': 'virtio'}) else: e(interface, 'model', None, {'type': 'e1000'}) e(interface, 'target', None, {'dev': nic.nicInternalName}) nic_qos(interface) def make_meta(): root = elements['root'] e(root, 'name', cmd.vmInstanceUuid) e(root, 'uuid', uuidhelper.to_full_uuid(cmd.vmInstanceUuid)) e(root, 'description', cmd.vmName) e(root, 'on_poweroff', 'destroy') e(root, 'on_crash', 'restart') e(root, 'on_reboot', 'restart') meta = e(root, 'metadata') zs = e(meta, 'zstack', usenamesapce=True) e(zs, 'internalId', str(cmd.vmInternalId)) e(zs, 'hostManagementIp', str(cmd.hostManagementIp)) clock = e(root, 'clock', None, {'offset': cmd.clock}) if cmd.clock == 'localtime': e(clock, 'timer', None, {'name': 'rtc', 'tickpolicy': 'catchup'}) e(clock, 'timer', None, {'name': 'pit', 'tickpolicy': 'delay'}) e(clock, 'timer', None, {'name': 'hpet', 'present': 'no'}) e(clock, 'timer', None, {'name': 'hypervclock', 'present': 'yes'}) def make_vnc(): devices = elements['devices'] if cmd.consolePassword == None: vnc = e(devices, 'graphics', None, {'type': 'vnc', 'port': '5900', 'autoport': 'yes'}) else: vnc = e(devices, 'graphics', None, {'type': 'vnc', 'port': '5900', 'autoport': 'yes', 'passwd': str(cmd.consolePassword)}) e(vnc, "listen", None, {'type': 'address', 'address': '0.0.0.0'}) def make_spice(): devices = elements['devices'] spice = e(devices, 'graphics', None, {'type': 'spice', 'port': '5900', 'autoport': 'yes'}) e(spice, "listen", None, {'type': 'address', 'address': '0.0.0.0'}) e(spice, "image", None, {'compression': 'auto_glz'}) e(spice, "jpeg", None, {'compression': 'always'}) e(spice, "zlib", None, {'compression': 'never'}) e(spice, "playback", None, {'compression': 'off'}) e(spice, "streaming", None, {'mode': cmd.spiceStreamingMode}) e(spice, "mouse", None, {'mode': 'client'}) e(spice, "filetransfer", None, {'enable': 'no'}) e(spice, "clipboard", None, {'copypaste': 'no'}) def make_usb_redirect(): if cmd.usbRedirect == "true": devices = elements['devices'] e(devices, 'controller', None, {'type': 'usb', 'model': 'ich9-ehci1'}) e(devices, 'controller', None, {'type': 'usb', 'model': 'ich9-uhci1', 'multifunction': 'on'}) e(devices, 'controller', None, {'type': 'usb', 'model': 'ich9-uhci2'}) e(devices, 'controller', None, {'type': 'usb', 'model': 'ich9-uhci3'}) chan = e(devices, 'channel', None, {'type': 'spicevmc'}) e(chan, 'target', None, {'type': 'virtio', 'name': 'com.redhat.spice.0'}) e(chan, 'address', None, {'type': 'virtio-serial'}) redirdev2 = e(devices, 'redirdev', None, {'type': 'spicevmc', 'bus': 'usb'}) e(redirdev2, 'address', None, {'type': 'usb', 'bus': '0', 'port': '2'}) redirdev3 = e(devices, 'redirdev', None, {'type': 'spicevmc', 'bus': 'usb'}) e(redirdev3, 'address', None, {'type': 'usb', 'bus': '0', 'port': '3'}) redirdev4 = e(devices, 'redirdev', None, {'type': 'spicevmc', 'bus': 'usb'}) e(redirdev4, 'address', None, {'type': 'usb', 'bus': '0', 'port': '4'}) redirdev5 = e(devices, 'redirdev', None, {'type': 'spicevmc', 'bus': 'usb'}) e(redirdev5, 'address', None, {'type': 'usb', 'bus': '0', 'port': '6'}) else: # make sure there are three default usb controllers, for usb 1.1/2.0/3.0 devices = elements['devices'] e(devices, 'controller', None, {'type': 'usb', 'index': '0'}) if not IS_AARCH64: e(devices, 'controller', None, {'type': 'usb', 'index': '1', 'model': 'ehci'}) e(devices, 'controller', None, {'type': 'usb', 'index': '2', 'model': 'nec-xhci'}) def make_video(): devices = elements['devices'] if IS_AARCH64: video = e(devices, 'video') e(video, 'model', None, {'type': 'virtio'}) elif cmd.videoType != "qxl": video = e(devices, 'video') e(video, 'model', None, {'type': str(cmd.videoType)}) else: for monitor in range(cmd.VDIMonitorNumber): video = e(devices, 'video') e(video, 'model', None, {'type': str(cmd.videoType)}) def make_audio_microphone(): if cmd.consoleMode == 'spice': devices = elements['devices'] e(devices, 'sound',None,{'model':'ich6'}) else: return def make_graphic_console(): if cmd.consoleMode == 'spice': make_spice() else: make_vnc() def make_addons(): if not cmd.addons: return devices = elements['devices'] channel = cmd.addons['channel'] if channel: basedir = os.path.dirname(channel.socketPath) linux.mkdir(basedir, 0777) chan = e(devices, 'channel', None, {'type': 'unix'}) e(chan, 'source', None, {'mode': 'bind', 'path': channel.socketPath}) e(chan, 'target', None, {'type': 'virtio', 'name': channel.targetName}) cephSecretKey = cmd.addons['ceph_secret_key'] cephSecretUuid = cmd.addons['ceph_secret_uuid'] if cephSecretKey and cephSecretUuid: VmPlugin._create_ceph_secret_key(cephSecretKey, cephSecretUuid) pciDevices = cmd.addons['pciDevice'] if pciDevices: make_pci_device(pciDevices) usbDevices = cmd.addons['usbDevice'] if usbDevices: make_usb_device(usbDevices) def make_pci_device(addresses): devices = elements['devices'] for addr in addresses: if match_pci_device(addr): hostdev = e(devices, "hostdev", None, {'mode': 'subsystem', 'type': 'pci', 'managed': 'yes'}) e(hostdev, "driver", None, {'name': 'vfio'}) source = e(hostdev, "source") e(source, "address", None, { "domain": hex(0) if len(addr.split(":")) == 2 else hex(int(addr.split(":")[0], 16)), "bus": hex(int(addr.split(":")[-2], 16)), "slot": hex(int(addr.split(":")[-1].split(".")[0], 16)), "function": hex(int(addr.split(":")[-1].split(".")[1], 16)) }) else: raise kvmagent.KvmError( 'can not find pci device for address %s' % addr) def make_usb_device(usbDevices): next_uhci_port = 2 next_ehci_port = 1 next_xhci_port = 1 devices = elements['devices'] for usb in usbDevices: if match_usb_device(usb): hostdev = e(devices, "hostdev", None, {'mode': 'subsystem', 'type': 'usb', 'managed': 'yes'}) source = e(hostdev, "source") e(source, "address", None, { "bus": str(int(usb.split(":")[0])), "device": str(int(usb.split(":")[1])) }) e(source, "vendor", None, { "id": hex(int(usb.split(":")[2], 16)) }) e(source, "product", None, { "id": hex(int(usb.split(":")[3], 16)) }) # get controller index from usbVersion # eg. 1.1 -> 0 # eg. 2.0.0 -> 1 # eg. 3 -> 2 bus = int(usb.split(":")[4][0]) - 1 if bus == 0: address = e(hostdev, "address", None, {'type': 'usb', 'bus': str(bus), 'port': str(next_uhci_port)}) next_uhci_port += 1 elif bus == 1: address = e(hostdev, "address", None, {'type': 'usb', 'bus': str(bus), 'port': str(next_ehci_port)}) next_ehci_port += 1 elif bus == 2: address = e(hostdev, "address", None, {'type': 'usb', 'bus': str(bus), 'port': str(next_xhci_port)}) next_xhci_port += 1 else: raise kvmagent.KvmError('unknown usb controller %s', bus) else: raise kvmagent.KvmError('cannot find usb device %s', usb) # TODO(WeiW) Validate here def match_pci_device(addr): return True def match_usb_device(addr): if len(addr.split(':')) == 5: return True else: return False def make_balloon_memory(): devices = elements['devices'] b = e(devices, 'memballoon', None, {'model': 'virtio'}) e(b, 'stats', None, {'period': '10'}) def make_console(): devices = elements['devices'] serial = e(devices, 'serial', None, {'type': 'pty'}) e(serial, 'target', None, {'port': '0'}) console = e(devices, 'console', None, {'type': 'pty'}) e(console, 'target', None, {'type': 'serial', 'port': '0'}) def make_sec_label(): root = elements['root'] e(root, 'seclabel', None, {'type': 'none'}) def make_controllers(): devices = elements['devices'] e(devices, 'controller', None, {'type': 'scsi', 'model': 'virtio-scsi'}) make_root() make_meta() make_cpu() make_memory() make_os() make_features() make_devices() make_video() make_audio_microphone() make_nics() make_volumes() make_cdrom() make_graphic_console() make_usb_redirect() make_addons() make_balloon_memory() make_console() make_sec_label() make_controllers() root = elements['root'] xml = etree.tostring(root) vm = Vm() vm.uuid = cmd.vmInstanceUuid vm.domain_xml = xml vm.domain_xmlobject = xmlobject.loads(xml) return vm
顯然,上述邏輯是在組裝一份xml,便於以後的libvirt使用。segmentfault
而後是api
vm.start(cmd.timeout)
能夠看到,這裏是直接調用了libvirt的sdk。app
這僅僅是一個調用流程。而在不少地方,來自MN的請求會直接調用linux的shell命令,詳情見linux.py
。(獲取雲盤大小、主存儲容量等)。框架
在基於擴展ZStack的Agent時,若是是一個全新的功能模塊,可能並不會形成和原有代碼的深度耦合。但若是在原有功能上的加強, 對原有代碼進行修改可能會致使咱們的業務邏輯和Utility的上游代碼耦合。而在沒有足夠人力來維護、開發ZStack時,咱們會將目標定爲可以及時跟上發佈版本。 所以,咱們要儘可能減小衝突。
舉個例子:咱們要對啓動vm的邏輯進行加強,添加一個本身的配置寫入xml。這段代碼若是寫進了vm_plugin.py,那麼就是一個耦合。耦合多了之後,跟上發佈版本就會很困難。
這是一個參考方案:
若是是引入一個全新的功能模塊,建議重寫一個項目。不管是代碼規範仍是自動化測試,均可以有一個很好的實踐。
若是是基於Utility的擴展,好比對於擴展的api——APIStartVmInstanceExMsg
。由上游發送http request時,將指定v2版本的agent。好比原有start vm會發送至path:AGENT_IP:7070/vm/start
;而若是咱們加強了這部分邏輯,將這段代碼copy至vm_plugin_ex.py,並註冊一個path,ex/vm/start
。固然port也要從新註冊一個,就像這樣::AGENT_IP:7071/ex/vm/start
。
一樣的,對linux.py擴展時,複製一個linux2.py來存放屬於咱們本身的擴展邏輯。