Hi, now, with qemu, virsh attach-disk doesn't work with inactive disks and we need to edit XML with virsh edit. IIUC, libvirt and virsh is designed as it is. But I want to modify domain XML via commandline tools - for middleware, which modify domains by scripting. - for concsoles, where curses can't work correctly. - for me, I can't remember XML definition detaisl ;) So, I write one. Following script is a script for modify domain XML and allows - add disks - delete disks - show list of disks I think most of elements defined in http://libvirt.org/formatdomain.html#elementsDisks is supported. But I'm an only qemu user and didn't test with Xen and other VMs. What I wanted to hear opinions as 'RFC' is - Can this be shipped with libvirt as one of a tool ? (with more documents) (If so, we'll write other scripts for cpu,network,memory,etc...) - If not, what is the best way other than making this as my house script ? I'm grad if this is shipped with libvirt is because catching new definition of XML is easy. - Doesn't this one work with your environment ? Example) [root@bluextal pydom]# ./virt-disk.py --domain Guest02 --virtio --source /dev/iscsi_lvm/disk02 Name : vdb Device Type : block Media Type : disk Bus Type : virtio Driver Name : qemu Driver Type : raw Driver Cache : default ReadWrite : ReadWrite Source : /dev/iscsi_lvm/disk02 Address: : AutoFill Add this device Y/N ? y [root@bluextal pydom]# virsh dumpxml Guest02 <domain type='kvm'> ..... <disk type='block' device='disk'> <driver name='qemu' type='raw'/> <source dev='/dev/iscsi_lvm/disk02'/> <target dev='vdb' bus='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/> </disk> == Thanks, -Kame == #!/usr/bin/python # # # 'virt-disk' is a command for maintaining disk entities in XML description # of libvirt's VM definition. Default vm assumes 'qemu' # # Copyright (C) 2011 Fujitsu LTD. # # KAMEZAWA Hiroyuki <kamezawa.hiroyu@xxxxxxxxxxxxxx> # # Usage: # # Brief help is # %virt-disk --help # # When you want to see list of disks of domain # %virt-disk --domain <domain name> --list # # When you want to remove 'vdb' # %virt-disk --domain <domain name> --delete vdb # # When you want to add block device as virtio block device. # %virt-disk --domain <domain name> --virtio --source /dev/to/mydisk # # When you want to add an IDE cdrom # %virt-disk --domain <domain name> --ide --media cdrom # --source /path/to/ISO.img # # When you want to add a network image as virtio disk. # %virt-disk --domain <domain name> --type network --protocol sheepdog # --host address:port --source resource_name --virtio # # This commad does # # - parse options. # - connect libvirt and get domain xml # - add all devices to disks[] List. # - add all pci address to PCIS[] List # - create new BlockDevice object with regard to options. # - create a XML entry of device # - insert and save it. # import xml.dom import xml.dom.minidom import os import stat import sys import libvirt import re,string from xml.dom import Node from optparse import OptionParser,OptionGroup PCIS = [] disks = [] class Address : pci = [] drive = [] def __init__(self, type) : self.type = type self.domain = None self.bus = None self.slot = None self.function = None self.controller = None self.function = None if type == 'pci' : PCIS.append(self) class BlockDevice : def __init__(self) : self.type = None self.media = None self.driver_cache = None self.driver_name = None self.driver_type = None self.driver_error_policy = None self.driver_io = None self.source = None self.protocol = None self.protocol_addr = None self.protocol_port = None self.target_dev = None self.target_bus = None self.readonly = False self.address_type = None self.address_bus = '' self.address_domain = '' self.address_function = '' self.address_slot = '' self.address_controller= '' self.address_unit='' self.boot_order = None self.shareable = False self.serial = None self.encrypt_format = None self.encrypt_type = None self.encrypt_uuid = None def get_attr(self, name, attr) : x = name.getAttribute(attr) if x == '' : return None return x def find_element(self, ent, name) : for member in ent.childNodes : if member.nodeName == name : return member return None def parse_info(self, name) : # parsing <disk ..... self.type = self.get_attr(name, 'type') self.media = self.get_attr(name, 'device') # parsing <driver .... driver = self.find_element(name, 'driver') if driver != None : self.driver_name = self.get_attr(driver, 'name') if self.driver_name == 'qemu' : self.driver_type = self.get_attr(driver, 'type') self.driver_cache = self.get_attr(driver, 'cache') self.driver_error_policy =\ self.get_attr(driver, 'error_policy') self.driver_io = self.get_attr(driver, 'io') # parsing <source source = self.find_element(name, 'source') if source != None : self.source = self.get_attr(source, 'dev') if self.source == None : self.source = self.get_attr(source, 'file') if self.source == None : self.protocol = self.get_attr(source, 'protocol') self.source = self.get_attr(source, 'name') else : self.source = None # # check protocol and host, port # if self.protocol != None : source = self.find_element(name, 'source') host = self.find_element(source, 'host') if host != None : self.protocol_host = self.get_attr(host, 'host') self.protocol_port = self.get_attr(host, 'port') # parsing <target target = self.find_element(name, 'target') if target != None : self.target_dev = self.get_attr(target, 'dev') self.target_bus = self.get_attr(target, 'bus') # check readonly if self.find_element(name, 'readonly') != None : self.readonly = True # check shareable if self.find_element(name, 'shareable') != None: self.shareable = True # check boot order boot = self.find_element(name, 'boot') if boot != None : self.boot_order = self.get_attr(boot, 'order') # check shareable if self.find_element(name, 'shareable') != None : self.shareable = True # check address address = self.find_element(name, 'address') if address != None : self.address_type = self.get_attr(address, 'type') if self.address_type == 'pci' : self.address_bus = self.get_attr(address, 'bus') self.address_slot = self.get_attr(address, 'slot') self.address_function = self.get_attr(address, 'function') self.address_domain = self.get_attr(address, 'domain') elif self.address_type == 'drive' : self.address_bus = self.get_attr(address, 'bus') self.address_controller = self.get_attr(address, 'controller') self.address_unit = self.get_attr(address, 'unit') # check serial ID serial = self.find_element(name, 'serial') if serial != None : for member in serial.childNodes : if member.nodeType == Node.TEXT_NODE : self.serial = member.data # check encryption encrypt = self.find_element(name, 'encryption') if encrypt != None : self.encrypt_format = self.get_attr(encrypt, 'format') sec = self.find_element(encrypt, 'secret') if sec != None : self.encrypt_type = self.get_attr(sec, 'type') self.encrypt_uuid = self.get_attr(sec, 'uuid') return True def show_ent(self, name, value) : if value != None : print '%-16s:\t%s' %(name, value) def show(self) : self.show_ent('Name', self.target_dev) self.show_ent('Device Type', self.type) self.show_ent('Media Type', self.media) self.show_ent('Bus Type', self.target_bus) self.show_ent('Driver Name', self.driver_name) self.show_ent('Driver Type', self.driver_type) self.show_ent('Driver Cache', self.driver_cache) self.show_ent('Driver I/O', self.driver_io) self.show_ent('Error Policy', self.driver_error_policy) if self.readonly : self.show_ent('ReadWrite', 'ReadOnly') else : self.show_ent('ReadWrite', 'ReadWrite') if self.shareable : self.show_ent('Shareable', 'Yes') self.show_ent('Device Boot Order',self.boot_order) self.show_ent('Protocol', self.protocol) self.show_ent('Hostname', self.protocol_addr) self.show_ent('Port', self.protocol_port) self.show_ent('Source', self.source) if self.address_type == None : self.show_ent('Address:', 'AutoFill') elif self.address_type == 'pci' : x = '%s/%s/%s' %\ (self.address_bus, self.address_slot, self.address_function) self.show_ent('PCI Address', x) elif self.address_type == 'drive' : x = '%s/%s/%s'\ %(self.address_controller, self.address_bus, self.address_unit) self.show_ent('Drive Index', x) if self.serial != None : self.show_ent('Serial', self.serial) if self.encrypt_format != None : self.show_ent('Encryption', self.encrypt_format) self.show_ent(' Type', self.encrypt_type) self.show_ent(' UUID', self.encrypt_uuid) def is_block_device(node) : if node.nodeName == 'disk' : return True return False def parse_address(addr) : type = addr.getAttribute('type') ent = Address(type) if ent.type == 'pci' : ent.bus = addr.getAttribute('bus') ent.slot = addr.getAttribute('slot') ent.function = addr.getAttribute('function') ent.domain = addr.getAttribute('domain') elif ent.type == 'drive' : ent.controller = addr.getAttribute('controller') ent.bus = addr.getAttribute('bus') ent.unit = addr.getAttribute('unit') # Nothing happens return True def check_pci_conflict(bus, slot, function) : sloti = int(slot, 16) for addr in PCIS : if int(addr.slot, 16) == sloti : return True return False def get_free_pci_slot(devname) : for i in range(3, 31) : #qemu allows only 32 slots x = str(i) if not check_pci_conflict('0x00', x, '0x00') : slot = '0x%02x'%(i) return ['0x00', slot, '0x00'] return [] def check_get_free_scsi_slot(index) : addr = scsi_index_to_addr(index) conflict = False for disk in disks : if disk.target_device != scsi: continue if disk.target_controller == addr[0] and\ disk.target_bus == addr[1] and\ disk.target_units == addr[2] : conflict = True break if conflict == False: return addr return [] def check_drive_conflict(type, controller, bus, unit) : for addr in drives : conflict = False for disk in disks : if disk.target_bus != 'type' : continue if disk.address_controller == controller and\ disk.address_bus == bus and\ disk.address_unit == unit : conflict = True break return conflict return True def check_device_conflict(devices, name) : for dev in devices : if dev.target_dev == name : return True return False def gen_scsi_name_from_index(index) : name = 'sd' if index < 26 : name = name + chr(ord('a') + index) elif index < (26 + (26 * 26)) : x = index / 26 - 1 y = index % 26 name = name + chr(ord('a') + x) name = name + chr(ord('a') + y) else : x = (index / 26 - 1) / 26 - 1 y = (index / 26 - 1) % 26 x = index % 26 name = name + chr(ord('a') + x) name = name + chr(ord('a') + y) name = name + chr(ord('a') + z) return name def gen_device_name(devices, head) : if head == 'hd' : for x in 'abcd' : name = 'hd' + x if not check_device_conflict(devices, name) : return name if head == 'vd' : for x in range(0, 26) : name = head + chr(ord('a') + x) if not check_device_conflict(devices, name) : return name if head == 'sd' : for index in range(0, 18278) : name = gen_scsi_name_from_index(index) if not check_device_conflict(devices, name) : return name return None # # Commandline Parser # Using optparse package. Deafault value is below. # usage='usage:%prog --domain Domain <options>....' parser = OptionParser(usage) def help_choice(help, list, default) : help = help + " " help = help + ",".join(list) if (default == True) : help = help + " default: %default" return help parser.add_option('-c', '--connect', action='store', type='string', dest='connect_uri', help='libvirtd connect URI') parser.add_option('-d', '--domain', action='store', type='string',dest="domain", help='domain name.') parser.add_option('-l', '--list', action='store_true', dest='show_list', help='see device list') parser.add_option('--delete', action='store', dest='delete', type='string' , help='remove device') # # 'block' or 'file' can be detected by --source option. # typeChoices=('block','file','network','dir') parser.add_option('--type', choices=typeChoices, dest='type', type='choice', help=help_choice('device type:', typeChoices, False)) mediaChoices=('disk','cdrom') parser.add_option('--media', choices=mediaChoices, type='choice',dest="media", help=help_choice('media type:', mediaChoices, True)) parser.add_option('--source', action='store', type='string', dest='source', help='select source file/device to be shown as disk') protocolChoices=('nbd','rbd', 'sheepdog') parser.add_option('--protocol', choices=protocolChoices, type='choice', dest='protocol', help=help_choice('netdisk protocol:', protocolChoices, False)) parser.add_option('--host', action='store', type='string', dest='host', help='<host:port> for rbd and sheepdog, network devices') parser.add_option('--devname', action='store', type='string', dest='devname', help='device name to be shown to guest(auto at default)') qemu_group = OptionGroup(parser, "Qemu Options") qemuFormatChoices=('raw','bochs','qcow2','qed') qemu_group.add_option('--qemu_disk_format', choices=qemuFormatChoices, type='choice', dest='qemu_format', help=help_choice('disk format(qemu):',qemuFormatChoices, True)) ioChoices=('threads','native') qemu_group.add_option('--io',choices=ioChoices, type='choice', dest='io', help=help_choice('io option(qemu):', ioChoices, False)) cacheChoices=('none','writeback','writethrough','default') qemu_group.add_option('--cache', choices=cacheChoices, type='choice', dest='cache', help=help_choice('cache policy:(qemu)', cacheChoices, True)) errorChoices=('stop','ignore','enospace') parser.add_option('--error_policy', choices=errorChoices, type='choice', dest='error_policy', help=help_choice('error policy:', errorChoices, False)) xen_group = OptionGroup(parser, "Xen Options") xenDriverChoices=('tap','tap2','phy','file') xen_group.add_option('--driver', choices=xenDriverChoices, type='choice', dest='driver_name', help=help_choice('Xen driver type:', xenDriverChoices, False)) xen_group.add_option('--aio', action='store_true', dest='xen_aio', help='using Xen aio driver') parser.add_option('--virtio', action='store_true', dest='virtio', help='create a virtio device') parser.add_option('--readonly', action='store_true', dest='readonly', help='attach device as read only') parser.add_option('--scsi', action='store_true', dest='scsi', help='create a scsi device') parser.add_option('--ide', action='store_true', dest='ide', help='create a ide device') parser.add_option('--pci', action='store', dest='pci', type='string', help='customize pci resource by hand as bus:slot:function') parser.add_option('--drive_id', action='store', dest='drive_id', type='string', help='customize ide/scsi resource ID as controller:bus:unit') parser.add_option('--yes', action='store_true', dest='yes', help='don\'t ask before save') parser.add_option('--nosave', action='store_true', dest='nosave', help='show created device XML instead of saving.') parser.add_option('--boot_order', action='store', dest='boot_order', type='int', help='boot order of the device', metavar='Num') parser.add_option('--shareable', action='store_true', dest='shareable', help='the device can be shareable between device') parser.add_option('--serial', action='store', dest='serial', type='string', help='optional serial ID of the device') EncChoices = ['qcow'] qemu_group.add_option('--encryption_format', choices=EncChoices, type='choice', dest='encryption_format', metavar='qcow', help='Only \"qcow\" is supported') EncTypeChoices =['passphrase'] qemu_group.add_option('--encryption_type', choices=EncTypeChoices, type='choice', dest='encryption_type', help=help_choice('encription_type', EncTypeChoices, True)) qemu_group.add_option('--encryption_uuid', action='store', metavar='UUID', dest='encryption_uuid', help='UUID for encrypted deivce') parser.add_option_group(qemu_group) parser.add_option_group(xen_group) parser.set_defaults(list=False, media='disk') parser.set_defaults(qemu_format='raw', cache='default', media='disk') parser.set_defaults(virtio=False, readonly=False, scsi=False, ide=False) parser.set_defaults(xen_aio=False,) parser.set_defaults(yes=False) (options, args) = parser.parse_args(sys.argv) # # Confirm domain XML is not updated since we opened it. # def check_domain_unchanged(origin, domain): if origin != domain.XMLDesc(0) : print 'Domain file may be updated while we open' print 'please retry' exit(1) # # Connect libvirt and get domain information. # if options.domain == None : print 'please specify domain to be modified' exit(1) try : conn = libvirt.open(options.connect_uri) except: print 'Can\'t connect libvirt with URI %s'%(options.connect_uri) exit(1) conn_uri = conn.getURI() info = re.split(':/', conn_uri) # From the name of connection URI, we detect vmname. vmname = info[0] # domain look up. domain = conn.lookupByName(options.domain) if domain == None: print 'Can\'t find domain %s' %(options.domain) exit(1) # read XML description. origin = domain.XMLDesc(0) dom = xml.dom.minidom.parseString(domain.XMLDesc(0)) # At 1st, we need to find <device> block. names = dom.getElementsByTagName('devices') if (names == None) : print '<device> element not found in %s\n' % filename exit(1) for devices in names : if devices.hasChildNodes() : for name in devices.childNodes : if (is_block_device(name)) : disk = BlockDevice() disk.parse_info(name) disks.append(disk) if name.hasChildNodes() : for elem in name.childNodes : if elem.nodeName == 'address' : addr = parse_address(elem) # # Show List. # if options.show_list == True : for disk in disks : disk.show() print '' exit(0) # delete is easy. if options.delete != None : disk = None for name in devices.childNodes : if (not is_block_device(name)) : continue disk = BlockDevice() disk.parse_info(name) if disk.target_dev == options.delete : devices.removeChild(name) break disk = None if disk == None : print 'no such device %s'%(options.delete) exit(1) if not options.yes : print 'You\'ll delete this device!' disk.show() x = raw_input('Really delete Y/N ? ') if x != 'Y' and x != 'y' : exit(0) str = dom.toxml() check_domain_unchanged(origin, domain) conn.defineXML(str) exit(1) # # Build a newdisk information from command line options # and default values. # newdisk = BlockDevice() if options.type != None : newdisk.type = options.type newdisk.media = options.media # # qemu only has a drive name as 'qemu'. 'type' and 'cache' are selectable. # if vmname == 'qemu' : newdisk.driver_name = 'qemu' newdisk.driver_type = options.qemu_format if options.cache == None : newdisk.driver_cache = 'default' else : newdisk.driver_cache = options.cache else : # Xen can select drive name. newdisk.driver_name = options.driver_name if options.xen_aio == True : newdisk.driver_type = 'aio' if options.error_policy != None : newdisk.driver_error_policy = options.error_policy if options.io != None : newdisk.io = options.io # Make readonly if crdom is specified. if options.media == 'cdrom' or options.readonly == True: newdisk.readonly = True; # Check Device Name and detect device bus from device name. if options.devname != None : if re.match('vd[a-z]', options.devname) : newdisk.target_dev = options.devname newdisk.target_bus = 'virtio' elif re.match('hd[a-d]', options.devname) : newdisk.target_dev = options.devname newdisk.target_bus = 'ide' elif re.match('sd[a-z]', options.devname) : newdisk.target_dev = options.devname newdisk.target_bus = 'scsi' else : newdisk.target_dev = options.devname # # Define device name automatically with regard to the bus. # if options.virtio == True : newdisk.target_bus = 'virtio' if options.devname == None: newdisk.target_dev = gen_device_name(disks, 'vd') if newdisk.target_dev == None: print 'failed to define virtio drive name as vd*' print 'please define by hand with --devname' exit(1) elif options.ide == True : newdisk.target_bus = 'ide' if options.devname == None: newdisk.target_dev = gen_device_name(disks, 'hd') if newdisk.target_dev == None : print 'failed to define ide drive name as hd*' print 'please define by hand with --devname' exit(1) elif options.scsi == True : newdisk.target_bus = 'scsi' if options.devname == None: newdisk.target_dev = gen_device_name(disks, 'sd') if newdisk.target_dev == None : print 'failed to define scsi drive name as sd*' print 'please define by hand with --devname' exit(1) # # If we can't detelct target bus, retrun error. # if newdisk.target_bus == None : print 'need to specify device name or target bus for drive' exit(1) # # If there is a device with the same name, error. # if check_device_conflict(disks, newdisk.target_dev) : print 'device name conflicts %s' %(newdisk.target_dev) print 'please specify an other name with --devname' # # Handle 'source' type. # if options.source != None and options.protocol == None: # No network case. check local FS. # Only absolute path is allowed. if options.source[0] != '/' : print 'please use absolute path for source %s:' %(options.source) exit(1) try: mode = os.stat(options.source)[stat.ST_MODE] except: print 'can\'t handle file %s' %(options.source) exit(1) # # check 'file' and 'block' # newdisk.source = options.source if stat.S_ISREG(mode) != 0: if newdisk.type == None : newdisk.type = 'file' if newdisk.type != 'file' : print '%s specified in --source is file' %(options.source) exit(1) if stat.S_ISBLK(mode) != 0 : if newdisk.type == None : newdisk.type = 'block' if newdisk.type != 'block' : print '%s specified in --source is blkdev' %(options.source) exit(1) if newdisk.type == None : print 'can\'t detect source type %s'%(options.source) elif options.type == 'network' : if options.protocol == None : print 'need to select protocol for network drive.' exit(1) newdisk.protocol = options.protocol if options.source == None : print 'source name for network should be privded --soruce' exit(1) newdisk.source = options.source if not re.match('[0-9a-zA-z\._-]+:[0-9]+', options.host) : print 'host should be specified in address:port manner' exit(1) (addr, port) = re.split(':', options.host) newdisk.protocol_addr = addr newdisk.protocol_port = port if options.media != 'cdrom' and options.source == None : print 'you need to specify source if not cdrom' exit(1) # # Define PCI/IDE/SCSI drive address. # (Usually, all this will be defined automatically.) # # format check if options.pci != None : if options.drive_id != None : print 'pci address and drive-id cannot be set at the same time' exit(1) if not re.match("0x[0-9a-f]+:0x[0-9a-f]+:0x[0-9-a-f]", options.pci) : print 'bad pci address ',options.pci print '0xXX:0xXX:0xXX is expected',options.pci exit(1) if options.drive_id != None : if not re.match("[0-9]+:[0-9]+:[0-9]+", options.drive_id) : print 'bad drive_id address', options.drive_id exit(1) # define BUS ID. # # In this case, device name should meet bus ID(especially IDE), # which the user specified. The user should specify device # name by himself. # if options.pci != None or options.drive_id != None : if options.devname == None : print 'We recommend that --drive_id should be used with --devname', print 'device name <-> drive ID relationship is at random, now' if options.pci != None : pci = re.split(':', options.pci) if '0x00' != pci[0] : print 'only bus 0x00 can be used in pci' exit(1) if check_pci_conflict(pci[0], pci[1], pci[2]) : print 'bus %s conflicts' % (options.pci) newdisk.address_type = 'pci' newdisk.address_bus = pci[0] newdisk.address_slot = pci[1] newdisk.address_function = pci[2] elif options.drive_id != None : drive = re.split(':', options.drive_id) if options.ide == True : if check_drive_conflict('ide', drive[0],drive[1],drive[2]) : print 'drive %s conflicts' % (options.drive_id) else : if check_drive_conflict('scsi',drive[0], drive[1], drive[2]) : print 'drive %s conflicts' % (options.drive_id) newdisk.address_type = 'drive' newdisk.address_controller = drive[0] newdisk.address_bus = drive[1] newdisk.address_unit = drive[2] if options.boot_order != None : newdisk.boot_order = str(options.boot_order) if options.shareable == True: newdisk.shareab;e = True if options.serial != None : newdisk.serial = options.serial # # Handle encryption # if options.encryption_format != None : if options.encryption_type == None or\ options.encryption_uuid == None : print 'encryption passphrase or UUID is not specified' exit(1) newdisk.encrypt_format = options.encryption_format newdisk.encrypt_type = options.encryption_type newdisk.encrypt_uuid = options.encryption_uuid # # Okay, we got all required information. Show the newdisk. # if options.yes != True : newdisk.show() # # Build XML from 'newdisk' # def append_attribute(doc, elem, attr, value) : x = doc.createAttribute(attr) elem.setAttributeNode(x) elem.setAttribute(attr, value) def append_text(doc, elem, text) : x = doc.createTextNode(text) elem.appendChild(x) def append_element(doc, parent, name, level) : append_text(doc, parent, '\n') while level > 0 : append_text(doc, parent, ' ') level = level - 1 element = doc.createElement(name) parent.appendChild(element) return element # <disk .... element = dom.createElement('disk') append_attribute(dom, element, 'device', newdisk.media) append_attribute(dom, element, 'type', newdisk.type) # # <driver ... # child = append_element(dom, element, 'driver', 3) append_attribute(dom, child, 'name', newdisk.driver_name) append_attribute(dom, child, 'type', newdisk.driver_type) if newdisk.driver_cache != None : append_attribute(dom, child, 'cache', newdisk.driver_cache) if newdisk.driver_error_policy != None : append_attribute(dom, child, 'error_policy', newdisk.driver_error_policy) if newdisk.driver_io != None : append_attribute(dom, child, 'io', newdisk.driver_io) # # <source.... # if newdisk.type == 'file' and newdisk.source != None: child = append_element(dom, element, 'source', 3) append_attribute(dom, child, 'file', options.source) elif newdisk.type == 'block' and newdisk.source != None: child = append_element(dom, element, 'source', 3) append_attribute(dom, child, 'dev', options.source) elif newdisk.type == 'network' and newdisk.protocol != None: child = append_element(dom, element, 'source', 3) append_attribute(dom, child, 'protocol', options.protocol) append_attribute(dom, child, 'name', options.source) host = append_element(dom, child, 'host', 4) address = re.split(':',options.host) append_attribute(dom, host, 'name', address[0]) append_attribute(dom, host, 'port', address[1]) # # <target.... # child = append_element(dom, element, 'target', 3) append_attribute(dom, child, 'dev', newdisk.target_dev) append_attribute(dom, child, 'bus', newdisk.target_bus) # # <address..... # libvirt will do auto-fill in typical case. # if newdisk.address_type != None : child = append_element(dom, element, 'address', 3) append_attribute(dom, child, 'type', newdisk.address_type) if newdisk.address_type == 'pci' : append_attribute(dom, child, 'bus', newdisk.address_bus) append_attribute(dom, child, 'slot', newdisk.address_slot) append_attribute(dom, child, 'function', newdisk.address_function) append_attribute(dom, child, 'domain', '0x0000') elif newdisk.address_type == 'drive' : append_attribute(dom, child, 'controller', newdisk.address_controller) append_attribute(dom, child, 'unit', newdisk.address_unit) append_attribute(dom, child, 'bus', newdisk.address_bus) append_attribute(dom, child, 'domain', '0x0000') # # <readonly # if newdisk.readonly == True: append_element(dom, element, 'readonly', 3) # # <shareable # if newdisk.shareable == True: append_element(dom, element, 'readonly', 3) # # <boot # if newdisk.boot_order != None: child = append_element(dom, element, 'boot', 3) append_attribute(dom, child, 'order', newdisk.boot_order) # # <serial # if newdisk.serial != None: child = append_element(dom, element, 'serial', 3) append_text(dom, child, newdisk.serial) # # <encryption # if newdisk.encrypt_format != None : child = append_element(dom, element, 'encryption', 3) append_attribute(dom, child, 'format', newdisk.encrypt_format) secret = append_element(dom, child, 'secret', 4) append_attribute(dom, secret, 'type', newdisk.encrypt_type) append_attribute(dom, secret, 'uuid', newdisk.encrypt_uuid) append_text(dom, child, '\n') # indent for </disk> append_text(dom, element, '\n') append_text(dom, element, ' ') if options.nosave == True : print '' print element.toxml('utf-8') exit(0) # # Insert newdisk to the tail of disks. # for devices in names : if not devices.hasChildNodes() : continue for name in devices.childNodes : if name.nodeName == 'controller' : break if name == None : append_text(dom, devices, ' ') x = dom.createTextNode('\n') devices.appendChild(element) devices.insertBefore(x, name) else: devices.insertBefore(element, name) x = dom.createTextNode('\n ') devices.insertBefore(x, name) if not options.yes : x = raw_input('Add this device Y/N ? ') if x != 'y' and x != 'Y' : exit(1) str = dom.toxml('utf-8') check_domain_unchanged(origin, domain) try: conn.defineXML(str) except: print 'Failed' -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list