In this paper, starting from purpose of moored floating column: segmentfault.com/blog/camile

background

In the previous article (ZStack source analysis of secondary development – extensible framework), we briefly looked at the ZStack core engine secondary development skills. In this article, we will take a look at the zStack-Utility (ZStack’s Agent) binary position.

example

Let’s look at the execution logic on the Agent using the ZStack management node calling startVm API as an example.

    def start(self) :
        http_server = kvmagent.get_http_server()

        http_server.register_async_uri(self.KVM_START_VM_PATH, self.start_vm)
Copy the code

First, you must register an HTTP path to accept reqeUST.

    @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)
            iferr ! ="":
                rsp.error = "%s, details: %s" % (err, rsp.error)
            rsp.success = False
        return jsonobject.dumps(rsp)
Copy the code

Go directly to the trunk logic, self._start_VM (CMD).

    @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 andvm.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)))
Copy the code

Key logic:

            vm = Vm.from_StartVmCmd(cmd)
            vm.start(cmd.timeout)
Copy the code

Look at the from_StartVmCmd

    @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 andnic.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'})
            elifcmd.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
Copy the code

Obviously, this logic is assembling an XML that libvirt can use later.

And then the

 vm.start(cmd.timeout)
Copy the code

As you can see, this is the SDK that calls libvirt directly.

This is just an invocation flow. In many places, requests from MN directly invoke Linux shell commands, as described in linux.py. Obtain the cloud disk size and primary storage capacity.

The problem

In Agent based on ZStack extension, if it is a new function module, it may not cause deep coupling with the original code. But if enhancements are made to the old code, changes to the old code could result in coupling our business logic with the Utility’s upstream code. When we don’t have the manpower to maintain and develop ZStack, we aim to keep up with releases. Therefore, we want to minimize conflict.

For example, we want to enhance the logic for starting the VM by adding a configuration of our own to write XML. This code, if written in vm_plugin.py, is a coupling. With more coupling, it can be difficult to keep up with releases.

The solution

Here is a reference scheme:

If introducing a completely new functional module, it is recommended to rewrite the project. Whether it’s code specifications or automated tests, you can have a good practice.

For Utility based extensions, such as APIStartVmInstanceExMsg, the API for extensions. When an UPSTREAM HTTP request is sent, the AGENT of v2 version is specified. For example, the original start VM will be sent to path: AGENT_IP:7070/ VM /start. If we enhance this logic, copy this code to vm_plugin_ex.py and register a path, ex/vm/start. Of course port also needs to be re-registered like this: : AGENT_IP:7071/ex/ VM /start.

Similarly, when extending linux.py, copy a linux2.py to hold our own extension logic.