[libvirt] [RFC] virt-disk : a command line tool for modify domain XML's disk of inactive domains.

KAMEZAWA Hiroyuki kamezawa.hiroyu at jp.fujitsu.com
Mon Feb 21 08:23:27 UTC 2011


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 at 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 at 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 at jp.fujitsu.com>
#
# 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'




More information about the libvir-list mailing list