~ubuntu-branches/ubuntu/quantal/xen-api/quantal

« back to all changes in this revision

Viewing changes to scripts/plugins/iovirt

  • Committer: Package Import Robot
  • Author(s): Jon Ludlam
  • Date: 2011-07-07 21:50:18 UTC
  • Revision ID: package-import@ubuntu.com-20110707215018-3t9ekbh7qy5y2b1p
Tags: upstream-1.3
ImportĀ upstreamĀ versionĀ 1.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# THIS IS AN EXPERIMENTAL, UNSUPPORTED PLUGIN FOR DEMONSTRATION AND TEST ONLY.
 
4
# PLEASE DO NOT USE IT FOR PRODUCTION ENVIRONMENTS.
 
5
# THIS PLUGIN CAN BE REMOVED OR MODIFIED AT ANY TIME WITHOUT PREVIOUS WARNING.
 
6
#
 
7
# A plugin for managing IO virtualization including SR-IOV
 
8
#
 
9
# Each VM has pass-through VFs configured using the "pci" and "sriovmacs"
 
10
# other-config keys. Each of these is a comma separated list of entries
 
11
# of the form "<index>/<pciid>" for pci and "<index>/<mac>" for sriovmacs
 
12
 
 
13
 
 
14
import XenAPI, inventory
 
15
import XenAPIPlugin
 
16
import os
 
17
import os.path
 
18
import stat
 
19
import glob
 
20
import re
 
21
import random
 
22
import subprocess
 
23
import xml.dom.minidom
 
24
import syslog
 
25
 
 
26
ipcmd = "/sbin/ip"
 
27
hookscripts = ["/etc/xapi.d/vm-pre-start/vm-pre-start-iovirt",
 
28
               "/etc/xapi.d/vm-pre-reboot/vm-pre-reboot-iovirt"
 
29
]
 
30
 
 
31
re_virtfn = re.compile("virtfn(\d+)$")
 
32
re_netdev = re.compile("(eth\d+)$")
 
33
re_hex16 = re.compile("^[0-9a-f]{4}$")
 
34
re_hex16x = re.compile("^0x([0-9a-f]{4})$")
 
35
 
 
36
def _install_hook_script():
 
37
    # Ensure that the hook script(s) used to configure SR-IOV VF MAC and
 
38
    # VLANs is present. If the script isn't present create it. This
 
39
    # function will be called whenever a VF is assigned. The intention
 
40
    # is that the hook is not used for users not using this plugin because
 
41
    # the additional API calls will add a small overhead to the VM.start
 
42
    # time which may hurt some use cases.
 
43
    for hookscript in hookscripts:
 
44
        if os.path.exists(hookscript):
 
45
            continue
 
46
        syslog.syslog("Creating iovirt hook script at %s" % (hookscript))
 
47
        hookdir = os.path.dirname(hookscript)
 
48
        if not os.path.exists(hookdir):
 
49
            os.makedirs(hookdir)
 
50
        f = file(hookscript, "w")
 
51
        f.write("""#!/bin/bash
 
52
#
 
53
# Call the iovirt plugin to set up SR-IOV VF MAC and VLAN config
 
54
# if required for this VF.
 
55
 
 
56
PLUGIN=iovirt
 
57
FN=prep_for_vm
 
58
 
 
59
for i
 
60
do
 
61
  case "$i" in
 
62
        -vmuuid) shift; VMUUID=$1; shift;;
 
63
  esac
 
64
done
 
65
 
 
66
if [ -z "$VMUUID" ]; then
 
67
    logger -t $(basename $0) "VM UUID not found in args"
 
68
fi
 
69
 
 
70
. /etc/xensource-inventory
 
71
 
 
72
if [ -z "$INSTALLATION_UUID" ]; then
 
73
    logger -t $(basename $0) "Could not determine host UUID"
 
74
fi
 
75
 
 
76
xe host-call-plugin plugin=$PLUGIN fn=$FN host-uuid=$INSTALLATION_UUID args:uuid=$VMUUID
 
77
"""
 
78
                )
 
79
        f.close()
 
80
        os.chmod(hookscript, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
 
81
        
 
82
def _get_vfs():
 
83
    # Find PCI devices with virtfns, find all virtfns associated with
 
84
    # network devices. Return dictionary of pciid => (vf#, ethdev)
 
85
    vfnodes = glob.glob("/sys/bus/pci/devices/*/virtfn*")
 
86
    vflist = {}
 
87
    for vfnode in vfnodes:
 
88
        vfpciid = os.path.basename(os.readlink(vfnode))
 
89
        r = re_virtfn.search(vfnode)
 
90
        if not r:
 
91
            raise "Failed to parse VF number for %s" % (vfnode)
 
92
        vfnum = r.group(1)
 
93
        netdevs = glob.glob("%s/physfn/net:eth*" % (vfnode))
 
94
        if len(netdevs) != 1:
 
95
            raise "Unexpected length of netdev list for %s: %s" % (vfnode, str(netdevs))
 
96
        r = re_netdev.search(netdevs[0])
 
97
        if not r:
 
98
            raise "Error parsing %s for net device" % (netdevs[0])
 
99
        netdev = r.group(1)
 
100
        vflist[vfpciid] = (vfnum, netdev)
 
101
    return vflist
 
102
 
 
103
def _get_devices(vendorid, deviceid):
 
104
    # Return device instances with the specified vendorid:deviceid
 
105
    # Returns a dictionary of pciid => (vendorid, deviceid)
 
106
    # Used for managing the assignment of non-SRIOV pools of devices.
 
107
    if not re_hex16.match(vendorid):
 
108
        raise "PCI vendor ID '%s' not in expected format" % (vendorid)
 
109
    if not re_hex16.match(deviceid):
 
110
        raise "PCI device ID '%s' not in expected format" % (deviceid)
 
111
    devices = {}
 
112
    devnodes = glob.glob("/sys/bus/pci/devices/*")
 
113
    for devnode in devnodes:
 
114
        pciid = os.path.basename(devnode)
 
115
        vendor, device = _get_vendor_and_device_ids(pciid)
 
116
        if vendor == vendorid and device == deviceid:
 
117
            devices[pciid] = (vendor, device)
 
118
    return devices
 
119
 
 
120
def _get_vendor_and_device_ids(pciid):
 
121
    devnode = ("/sys/bus/pci/devices/%s" % (pciid))
 
122
    vendor = None
 
123
    device = None
 
124
    if os.path.exists("%s/vendor" % (devnode)):
 
125
        f = file("%s/vendor" % (devnode))
 
126
        d = f.read()
 
127
        f.close()
 
128
        vendorid = re_hex16x.search(d)
 
129
        if vendorid:
 
130
            vendor = vendorid.group(1)
 
131
    if os.path.exists("%s/device" % (devnode)):
 
132
        f = file("%s/device" % (devnode))
 
133
        d = f.read()
 
134
        f.close()
 
135
        deviceid = re_hex16x.search(d)
 
136
        if deviceid:
 
137
            device = deviceid.group(1)
 
138
    return vendor, device
 
139
 
 
140
def _get_assignments(session):
 
141
    # Return a dict of PCI devices assigned to VMs. This currently assumes
 
142
    # a pool of one host. In a pool this will be overly restrictive (i.e.
 
143
    # a VM started on another host will be reported as having one of our
 
144
    # host's devices) but it'll do for now.
 
145
    # Returns a dictionary PCIID => (VMUUID, index, vendorid, deviceid, mac, vlan)
 
146
    # where mac and vlan are only (optionally) available for SR-IOV VFs.
 
147
    expr = 'field "is_a_template" = "false" and field "is_a_snapshot" = "false"'
 
148
    vms = session.xenapi.VM.get_all_records_where(expr)
 
149
    devices = {}
 
150
    for vm in vms.values():
 
151
        # Ignore VMs with no PCI passthrough config or an empty config
 
152
        if not vm['other_config'].has_key('pci'):
 
153
            continue
 
154
        if not vm['other_config']['pci']:
 
155
            continue
 
156
        vmuuid = vm['uuid']
 
157
        assignments = _get_vm_assignments(session, vmuuid)
 
158
        for index in assignments.keys():
 
159
            devices[assignments[index][0]] = (vmuuid,
 
160
                                              index,
 
161
                                              assignments[index][1],
 
162
                                              assignments[index][2],
 
163
                                              assignments[index][3],
 
164
                                              assignments[index][4])
 
165
    return devices
 
166
 
 
167
def enable_iommu(session, args):
 
168
    rc = os.system("@BASE_PATH@/libexec/xen-cmdline --set-xen iommu=1")
 
169
    return str(rc == 0)
 
170
 
 
171
#def list_assigned_vfs(session, args):
 
172
#    """List VFs on this host that are assigned to VMs that can run here."""
 
173
#    x = _get_assignments(session)
 
174
#    return `x`
 
175
 
 
176
def _get_vm_assignments(session, vmuuid):
 
177
    # Return a dictionary of index => (PCIID, vendorid, deviceid, MAC, VLAN)
 
178
    # for assignments for the specified VM.
 
179
    # This includes SR-IOV NIC VFs and other PCI pass through
 
180
    # devices. (MAC and VLAN only includes for the former)
 
181
    re_pci = re.compile("^(\d+)/([0-9a-f:\.]+)$")
 
182
    re_vlan = re.compile("^(\d+)/([0-9]+)$")
 
183
    re_mac = re.compile("^(\d+)/([0-9a-fA-F:]+)$")
 
184
 
 
185
    vmref = session.xenapi.VM.get_by_uuid(vmuuid)
 
186
    vm = session.xenapi.VM.get_record(vmref)
 
187
    assignments = {}
 
188
 
 
189
    # Parse the SR-IOV MAC config string
 
190
    macs = {}
 
191
    if vm['other_config'].has_key('sriovmacs') and vm['other_config']['sriovmacs']:
 
192
        for x in vm['other_config']['sriovmacs'].split(","):
 
193
            r = re_mac.search(x)
 
194
            if not r:
 
195
                raise "Failed to parse MAC config '%s' for VM %s" % x, vmuuid
 
196
            macs[int(r.group(1))] = r.group(2)
 
197
    
 
198
    # Parse the SR-IOV VLAN config string
 
199
    vlans = {}
 
200
    if vm['other_config'].has_key('sriovvlans') and vm['other_config']['sriovvlans']:
 
201
        for x in vm['other_config']['sriovvlans'].split(","):
 
202
            r = re_vlan.search(x)
 
203
            if not r:
 
204
                raise "Failed to parse VLAN config '%s' for VM %s" % x, vmuuid
 
205
            vlans[int(r.group(1))] = r.group(2)
 
206
    
 
207
    # Parse the PCI config string
 
208
    if vm['other_config'].has_key('pci') and vm['other_config']['pci']:
 
209
        for x in vm['other_config']['pci'].split(","):
 
210
            r = re_pci.search(x)
 
211
            if not r:
 
212
                raise "Failed to parse PCI config '%s' for VM %s" % x, vmuuid
 
213
            vendorid, deviceid = _get_vendor_and_device_ids(r.group(2))
 
214
            if macs.has_key(int(r.group(1))):
 
215
                mac = macs[int(r.group(1))]
 
216
            else:
 
217
                mac = None
 
218
            if vlans.has_key(int(r.group(1))):
 
219
                vlan = vlans[int(r.group(1))]
 
220
            else:
 
221
                vlan = None
 
222
            assignments[int(r.group(1))] = (r.group(2), vendorid, deviceid, mac, vlan)
 
223
 
 
224
    return assignments
 
225
 
 
226
def _randomMAC():
 
227
    """Return a random MAC in the locally administered range"""
 
228
    o1 = (random.randint(0, 63) << 2) | 2
 
229
    o2 = random.randint(0, 255)
 
230
    o3 = random.randint(0, 255)
 
231
    o4 = random.randint(0, 255)
 
232
    o5 = random.randint(0, 255)
 
233
    o6 = random.randint(0, 255)
 
234
    return "%02x:%02x:%02x:%02x:%02x:%02x" % (o1, o2, o3, o4, o5, o6)
 
235
 
 
236
def _set_vm_assignments(session, vmuuid, assignments):
 
237
    # Set the PCI (and MAC and VLAN for SR-IOV) assignments for the
 
238
    # specified VM removing any existing config first. assignments is a
 
239
    # dictionary index => (pciid, vendorid, deviceid, mac, vlan) where
 
240
    # vendorid and deviceid need not be set.
 
241
    indexlist = assignments.keys()
 
242
    indexlist.sort()
 
243
    pcilist = []
 
244
    maclist = []
 
245
    vlanlist = []
 
246
    for i in indexlist:
 
247
        pcilist.append("%u/%s" % (i, assignments[i][0]))
 
248
        if assignments[i][3]:
 
249
            maclist.append("%u/%s" % (i, assignments[i][3]))
 
250
        if assignments[i][4]:
 
251
            vlanlist.append("%u/%s" % (i, assignments[i][4]))
 
252
    pci = ",".join(pcilist)
 
253
    mac = ",".join(maclist)
 
254
    vlan = ",".join(vlanlist)
 
255
    vmref = session.xenapi.VM.get_by_uuid(vmuuid)
 
256
    try:
 
257
        session.xenapi.VM.remove_from_other_config(vmref, "pci")
 
258
    except:
 
259
        pass
 
260
    try:
 
261
        session.xenapi.VM.remove_from_other_config(vmref, "sriovmacs")
 
262
    except:
 
263
        pass
 
264
    try:
 
265
        session.xenapi.VM.remove_from_other_config(vmref, "sriovvlans") 
 
266
    except:
 
267
        pass
 
268
    if len(pci) > 0:
 
269
        session.xenapi.VM.add_to_other_config(vmref, "pci", pci)
 
270
    if len(mac) > 0:
 
271
        session.xenapi.VM.add_to_other_config(vmref, "sriovmacs", mac)
 
272
    if len(vlan) > 0:
 
273
        session.xenapi.VM.add_to_other_config(vmref, "sriovvlans", vlan)
 
274
    
 
275
def assign_free_vf(session, args):
 
276
    """Assign a free VF on this host to the specified VM."""
 
277
 
 
278
    # Ensure the hook script exists to configure VFs before VM.start
 
279
    _install_hook_script()
 
280
    
 
281
    assigned = _get_assignments(session)
 
282
    vfs = _get_vfs()
 
283
    for vfid in assigned.keys():
 
284
        if vfid in vfs:
 
285
            del vfs[vfid]
 
286
    if not args.has_key("uuid"):
 
287
        raise "No VM UUID specified, please use the 'uuid' argument"
 
288
    vmuuid = args["uuid"]
 
289
    ethdev = None
 
290
    vlan = None
 
291
    pifref = None
 
292
    
 
293
    # Allow the caller to specify a the eth device from which the VF should
 
294
    # be allocated
 
295
    if args.has_key("ethdev"):
 
296
        ethdev = args["ethdev"]
 
297
 
 
298
    # Specify by network UUID. If this is a VLAN network then this forces the
 
299
    # specification of a VLAN tag for the VF.
 
300
    if args.has_key("nwuuid"):
 
301
        nwuuid = args["nwuuid"]
 
302
        nwref = session.xenapi.network.get_by_uuid(nwuuid)
 
303
        pifrefs = session.xenapi.network.get_PIFs(nwref)
 
304
        # Find PIF for this host
 
305
        hostuuid = inventory.get_localhost_uuid()
 
306
        hostref = session.xenapi.host.get_by_uuid(hostuuid)
 
307
        for pref in pifrefs:
 
308
            if session.xenapi.PIF.get_host(pref) == hostref:
 
309
                pifref = pref
 
310
                break
 
311
        if not pifref:
 
312
            raise "Could not find PIF record for network %s on this host" % (nwuuid)
 
313
 
 
314
    # Specify by PIF UUID. If this is a VLAN PIF then this forces the
 
315
    # specification of a VLAN tag for the VF.
 
316
    if args.has_key("pifuuid"):
 
317
        pifuuid = args["pifuuid"]
 
318
        pifref = session.xenapi.PIF.get_by_uuid(pifuuid)
 
319
 
 
320
    if pifref:
 
321
        v = str(session.xenapi.PIF.get_VLAN(pifref))
 
322
        if v and v != "-1":
 
323
            vlan = v
 
324
        ethdev = session.xenapi.PIF.get_device(pifref)
 
325
 
 
326
    # If no ethdev, PIF or network is specified then reject
 
327
    if not ethdev:
 
328
        raise "Must specify eth device by device, PIF or network."
 
329
    
 
330
    # If the caller specified a pass through index use that otherwise
 
331
    # find the first one not currently configured.
 
332
    ourassignments = _get_vm_assignments(session, vmuuid)
 
333
    if args.has_key("index"):
 
334
        index = int(args["index"])
 
335
    else:
 
336
        i = 0
 
337
        while True:
 
338
            if not i in ourassignments.keys():
 
339
                index = i
 
340
                break
 
341
            i = i + 1
 
342
 
 
343
    # Use the user specified MAC or create a local adminstered one
 
344
    if args.has_key("mac"):
 
345
        mac = args["mac"]
 
346
    else:
 
347
        mac = _randomMAC()
 
348
 
 
349
    if args.has_key("vlan"):
 
350
        if vlan and args["vlan"] != vlan:
 
351
            raise "Cannot override PIF VLAN %s" % (vlan)
 
352
        vlan = args["vlan"]
 
353
    
 
354
    # Choose a suitable VF. Preference is for lower numbered VFs
 
355
    revdict = {}
 
356
    for vfid in vfs.keys():
 
357
        rvf, rdev = vfs[vfid]
 
358
        rkey = "%08u%s" % (int(rvf), rdev)
 
359
        revdict[rkey] = vfid
 
360
    revdictkeys = revdict.keys()
 
361
    revdictkeys.sort()
 
362
    vfidlist = [revdict[x] for x in revdictkeys]
 
363
    myid = None
 
364
    for vfid in vfidlist:
 
365
        if not ethdev:
 
366
            myid = vfid
 
367
            break
 
368
        if ethdev == vfs[vfid][1]:
 
369
            myid = vfid
 
370
            break
 
371
    if not myid:
 
372
        if ethdev:
 
373
            raise "No spare VF on %s" % (ethdev)
 
374
        raise "No spare VF"
 
375
    
 
376
    # Set up the config for the VM. No need to fill out the vendor and
 
377
    # device id fields for this.
 
378
    ourassignments[index] = (myid, None, None, mac, vlan)
 
379
    _set_vm_assignments(session, vmuuid, ourassignments)
 
380
 
 
381
    vfnum, vfeth = vfs[myid]
 
382
 
 
383
    dom = xml.dom.minidom.Document()
 
384
    element = dom.createElement("iovirt")
 
385
    dom.appendChild(element)
 
386
    
 
387
    entry = dom.createElement("vf")
 
388
    element.appendChild(entry)
 
389
 
 
390
    subentry = dom.createElement("pciid")
 
391
    entry.appendChild(subentry)
 
392
    subentry.appendChild(dom.createTextNode(myid))
 
393
 
 
394
    subentry = dom.createElement("device")
 
395
    entry.appendChild(subentry)
 
396
    subentry.appendChild(dom.createTextNode(vfeth))
 
397
    
 
398
    subentry = dom.createElement("vfnum")
 
399
    entry.appendChild(subentry)
 
400
    subentry.appendChild(dom.createTextNode(vfnum))
 
401
 
 
402
    if mac:
 
403
        subentry = dom.createElement("mac")
 
404
        entry.appendChild(subentry)
 
405
        subentry.appendChild(dom.createTextNode(mac))
 
406
    if vlan:
 
407
        subentry = dom.createElement("vlan")
 
408
        entry.appendChild(subentry)
 
409
        subentry.appendChild(dom.createTextNode(vlan))
 
410
 
 
411
    aentry = dom.createElement("assigned")
 
412
    entry.appendChild(aentry)
 
413
    
 
414
    subentry = dom.createElement("vm")
 
415
    aentry.appendChild(subentry)
 
416
    subentry.appendChild(dom.createTextNode(vmuuid))
 
417
    
 
418
    subentry = dom.createElement("index")
 
419
    aentry.appendChild(subentry)
 
420
    subentry.appendChild(dom.createTextNode(str(index)))
 
421
    
 
422
    return dom.toprettyxml()
 
423
 
 
424
def unassign_vf(session, args, genericpci=False):
 
425
    """Unassign a VF/device from a VM."""
 
426
    if not args.has_key("uuid"):
 
427
        raise "No VM UUID specified, please use the 'uuid' argument"
 
428
    vmuuid = args["uuid"]
 
429
 
 
430
    # Specify the VF/device by either PCI ID, index, or ethX+VFn
 
431
    pciid = None
 
432
    index = None
 
433
    vfdisplay = ""
 
434
    if args.has_key("index"):
 
435
        index = int(args["index"])
 
436
    if args.has_key("pciid"):
 
437
        pciid = args["pciid"]
 
438
        vfdisplay = pciid
 
439
    if args.has_key("ethdev") and args.has_key("vf") and not genericpci:
 
440
        ethdev = args["ethdev"]
 
441
        vfnum = args["vf"]
 
442
        vfdisplay = "%s VF %s" % (ethdev, vfnum)
 
443
        vfs = _get_vfs()
 
444
        for vfpciid in vfs.keys():
 
445
            if (vfnum, ethdev) == vfs[vfpciid]:
 
446
                pciid = vfpciid
 
447
                break
 
448
        if not pciid:
 
449
            raise "Unable to find PCI ID for " + vfdisplay
 
450
 
 
451
    if not pciid and index == None:
 
452
        if genericpci:
 
453
            raise "Need to specify either a pciid or index"
 
454
        else:
 
455
            raise "Need to specify either a pciid, index or ethdev and vf"
 
456
 
 
457
    # Current assignments
 
458
    current = _get_vm_assignments(session, vmuuid)
 
459
 
 
460
    # Remove the specified ID
 
461
    if pciid:
 
462
        if not pciid in [x[0] for x in current.values()]:
 
463
            raise "VM %s does not have %s assigned" % (vmuuid, vfdisplay)
 
464
        for i in current.keys():
 
465
            if current[i][0] == pciid:
 
466
                del current[i]
 
467
    else:
 
468
        if not current.has_key(index):
 
469
            raise "VM %s does not have PCI passthrough index %u" % (index)
 
470
        del current[index]
 
471
 
 
472
    # Update the config
 
473
    _set_vm_assignments(session, vmuuid, current)
 
474
    return ""
 
475
 
 
476
def assign_free_pci_device(session, args):
 
477
    """Assign a free PCI device on this host to the specified VM."""
 
478
    assigned = _get_assignments(session)
 
479
    if not args.has_key("vendorid"):
 
480
        raise "No vendor ID specified, please use the 'vendorid' argument"
 
481
    if not args.has_key("deviceid"):
 
482
        raise "No device ID specified, please use the 'deviceid' argument"
 
483
    # _get_devices will check syntax of the args
 
484
    vendorid = args["vendorid"]
 
485
    deviceid = args["deviceid"]
 
486
    devices = _get_devices(vendorid, deviceid)
 
487
 
 
488
    for pciid in assigned.keys():
 
489
        if pciid in devices:
 
490
            del devices[pciid]
 
491
    if not args.has_key("uuid"):
 
492
        raise "No VM UUID specified, please use the 'uuid' argument"
 
493
    vmuuid = args["uuid"]
 
494
 
 
495
    # If the caller specified a pass through index use that otherwise
 
496
    # find the first one not currently configured.
 
497
    ourassignments = _get_vm_assignments(session, vmuuid)
 
498
    if args.has_key("index"):
 
499
        index = int(args["index"])
 
500
    else:
 
501
        i = 0
 
502
        while True:
 
503
            if not i in ourassignments.keys():
 
504
                index = i
 
505
                break
 
506
            i = i + 1
 
507
 
 
508
    # Choose a suitable device. Preference is for lower numbered PCIIDs
 
509
    pciids = devices.keys()
 
510
    pciids.sort()
 
511
    myid = None
 
512
    if len(pciids) > 0:
 
513
        myid = pciids[0]
 
514
    if not myid:
 
515
        raise "No spare %s:%s device" % (vendorid, deviceid)
 
516
    
 
517
    # Set up the config for the VM. No need to fill out the vendor and
 
518
    # device id fields for this.
 
519
    ourassignments[index] = (myid, None, None, None, None)
 
520
    _set_vm_assignments(session, vmuuid, ourassignments)
 
521
 
 
522
    dom = xml.dom.minidom.Document()
 
523
    element = dom.createElement("iovirt")
 
524
    dom.appendChild(element)
 
525
    
 
526
    entry = dom.createElement("pcidevice")
 
527
    element.appendChild(entry)
 
528
    
 
529
    subentry = dom.createElement("pciid")
 
530
    entry.appendChild(subentry)
 
531
    subentry.appendChild(dom.createTextNode(myid))
 
532
    
 
533
    subentry = dom.createElement("vendorid")
 
534
    entry.appendChild(subentry)
 
535
    subentry.appendChild(dom.createTextNode(vendorid))
 
536
    
 
537
    subentry = dom.createElement("deviceid")
 
538
    entry.appendChild(subentry)
 
539
    subentry.appendChild(dom.createTextNode(deviceid))
 
540
    
 
541
    aentry = dom.createElement("assigned")
 
542
    entry.appendChild(aentry)
 
543
    
 
544
    subentry = dom.createElement("vm")
 
545
    aentry.appendChild(subentry)
 
546
    subentry.appendChild(dom.createTextNode(vmuuid))
 
547
    
 
548
    subentry = dom.createElement("index")
 
549
    aentry.appendChild(subentry)
 
550
    subentry.appendChild(dom.createTextNode(str(index)))
 
551
    
 
552
    return dom.toprettyxml()
 
553
 
 
554
def unassign_pci_device(session, args):
 
555
    return unassign_vf(session, args, genericpci=True)
 
556
    
 
557
def show_summary(session, args):
 
558
    """Return a textual summary of SR-IOV configuarion of this host."""
 
559
    assigned = _get_assignments(session)
 
560
    vfs = _get_vfs()
 
561
    vfids = vfs.keys()
 
562
    vfids.sort()
 
563
    dom = xml.dom.minidom.Document()
 
564
    element = dom.createElement("iovirt")
 
565
    dom.appendChild(element)
 
566
 
 
567
    for vfid in vfids:
 
568
        entry = dom.createElement("vf")
 
569
        element.appendChild(entry)
 
570
        
 
571
        vfnum, vfeth = vfs[vfid]
 
572
 
 
573
        subentry = dom.createElement("pciid")
 
574
        entry.appendChild(subentry)
 
575
        subentry.appendChild(dom.createTextNode(vfid))
 
576
 
 
577
        subentry = dom.createElement("device")
 
578
        entry.appendChild(subentry)
 
579
        subentry.appendChild(dom.createTextNode(vfeth))
 
580
 
 
581
        subentry = dom.createElement("vfnum")
 
582
        entry.appendChild(subentry)
 
583
        subentry.appendChild(dom.createTextNode(vfnum))
 
584
 
 
585
        if assigned.has_key(vfid):
 
586
            vmuuid, vmnum, vendorid, deviceid, mac, vlan = assigned[vfid]
 
587
            aentry = dom.createElement("assigned")
 
588
            entry.appendChild(aentry)
 
589
 
 
590
            subentry = dom.createElement("vm")
 
591
            aentry.appendChild(subentry)
 
592
            subentry.appendChild(dom.createTextNode(vmuuid))
 
593
 
 
594
            subentry = dom.createElement("index")
 
595
            aentry.appendChild(subentry)
 
596
            subentry.appendChild(dom.createTextNode(str(vmnum)))
 
597
 
 
598
            # MAC and VLAN go in the parent node
 
599
            if mac:
 
600
                subentry = dom.createElement("mac")
 
601
                entry.appendChild(subentry)
 
602
                subentry.appendChild(dom.createTextNode(mac))
 
603
            if vlan:
 
604
                subentry = dom.createElement("vlan")
 
605
                entry.appendChild(subentry)
 
606
                subentry.appendChild(dom.createTextNode(vlan))
 
607
 
 
608
    return dom.toprettyxml()
 
609
 
 
610
def list_pci_devices(session, args):
 
611
    """Return a list of PCI devices of the specified vendorid:deviceid and their assignments."""
 
612
    if not args.has_key("vendorid"):
 
613
        raise "No vendor ID specified, please use the 'vendorid' argument"
 
614
    if not args.has_key("deviceid"):
 
615
        raise "No device ID specified, please use the 'deviceid' argument"
 
616
    # _get_devices will check syntax of the args
 
617
    vendorid = args["vendorid"]
 
618
    deviceid = args["deviceid"]
 
619
    devices = _get_devices(vendorid, deviceid)
 
620
    assigned = _get_assignments(session)
 
621
    pciids = devices.keys()
 
622
    pciids.sort()
 
623
    dom = xml.dom.minidom.Document()
 
624
    element = dom.createElement("iovirt")
 
625
    dom.appendChild(element)
 
626
    for pciid in pciids:
 
627
        entry = dom.createElement("pcidevice")
 
628
        element.appendChild(entry)
 
629
        
 
630
        subentry = dom.createElement("pciid")
 
631
        entry.appendChild(subentry)
 
632
        subentry.appendChild(dom.createTextNode(pciid))
 
633
 
 
634
        subentry = dom.createElement("vendorid")
 
635
        entry.appendChild(subentry)
 
636
        subentry.appendChild(dom.createTextNode(vendorid))
 
637
 
 
638
        subentry = dom.createElement("deviceid")
 
639
        entry.appendChild(subentry)
 
640
        subentry.appendChild(dom.createTextNode(deviceid))
 
641
 
 
642
        if assigned.has_key(pciid):
 
643
            vmuuid, vmnum, vendorid, deviceid, mac, vlan = assigned[pciid]
 
644
            aentry = dom.createElement("assigned")
 
645
            entry.appendChild(aentry)
 
646
 
 
647
            subentry = dom.createElement("vm")
 
648
            aentry.appendChild(subentry)
 
649
            subentry.appendChild(dom.createTextNode(vmuuid))
 
650
 
 
651
            subentry = dom.createElement("index")
 
652
            aentry.appendChild(subentry)
 
653
            subentry.appendChild(dom.createTextNode(str(vmnum)))
 
654
 
 
655
            if mac:
 
656
                subentry = dom.createElement("mac")
 
657
                aentry.appendChild(subentry)
 
658
                subentry.appendChild(dom.createTextNode(mac))
 
659
            if vlan:
 
660
                subentry = dom.createElement("vlan")
 
661
                aentry.appendChild(subentry)
 
662
                subentry.appendChild(dom.createTextNode(vlan))
 
663
 
 
664
    return dom.toprettyxml()
 
665
 
 
666
def get_vm(session, args):
 
667
    """Return a description of the SR-IOV config for the specified VM."""
 
668
    if not args.has_key("uuid"):
 
669
        raise "No VM UUID specified, please use the 'uuid' argument"
 
670
    vmuuid = args["uuid"]
 
671
    vfs = _get_vfs()
 
672
    current = _get_vm_assignments(session, vmuuid)
 
673
    indexlist = current.keys()
 
674
    indexlist.sort()
 
675
    dom = xml.dom.minidom.Document()
 
676
    element = dom.createElement("iovirt")
 
677
    dom.appendChild(element)
 
678
    vmentry = dom.createElement("vm")
 
679
    element.appendChild(vmentry)
 
680
    subentry = dom.createElement("uuid")
 
681
    vmentry.appendChild(subentry)
 
682
    subentry.appendChild(dom.createTextNode(vmuuid))
 
683
    for i in indexlist:
 
684
        entry = dom.createElement("passthrough")
 
685
        vmentry.appendChild(entry)
 
686
        subentry = dom.createElement("index")
 
687
        entry.appendChild(subentry)
 
688
        subentry.appendChild(dom.createTextNode(str(i)))
 
689
        
 
690
        pciid, vendorid, deviceid, mac, vlan = current[i]
 
691
        
 
692
        subentry = dom.createElement("pciid")
 
693
        entry.appendChild(subentry)
 
694
        subentry.appendChild(dom.createTextNode(pciid))
 
695
        subentry = dom.createElement("vendorid")
 
696
        entry.appendChild(subentry)
 
697
        subentry.appendChild(dom.createTextNode(vendorid))
 
698
        subentry = dom.createElement("deviceid")
 
699
        entry.appendChild(subentry)
 
700
        subentry.appendChild(dom.createTextNode(deviceid))
 
701
        if mac:
 
702
            subentry = dom.createElement("mac")
 
703
            entry.appendChild(subentry)
 
704
            subentry.appendChild(dom.createTextNode(mac))
 
705
        if vlan:
 
706
            subentry = dom.createElement("vlan")
 
707
            entry.appendChild(subentry)
 
708
            subentry.appendChild(dom.createTextNode(vlan))
 
709
        if pciid in vfs:
 
710
            vfnum, ethdev = vfs[pciid]
 
711
            subentry = dom.createElement("vfnum")
 
712
            entry.appendChild(subentry)
 
713
            subentry.appendChild(dom.createTextNode(vfnum))
 
714
            subentry = dom.createElement("device")
 
715
            entry.appendChild(subentry)
 
716
            subentry.appendChild(dom.createTextNode(ethdev))
 
717
            subentry = dom.createElement("pttype")
 
718
            entry.appendChild(subentry)
 
719
            subentry.appendChild(dom.createTextNode("vf"))
 
720
        else:
 
721
            subentry = dom.createElement("pttype")
 
722
            entry.appendChild(subentry)
 
723
            subentry.appendChild(dom.createTextNode("pcidevice"))
 
724
    return dom.toprettyxml()
 
725
 
 
726
def prep_for_vm(session, args):
 
727
    if not args.has_key("uuid"):
 
728
        raise "No VM UUID specified, please use the 'uuid' argument"
 
729
    vmuuid = args["uuid"]
 
730
    current = _get_vm_assignments(session, vmuuid)
 
731
    vfs = _get_vfs()
 
732
    reply = []
 
733
    for i in current.keys():
 
734
        pciid, vendorid, deviceid, mac, vlan = current[i]
 
735
        if not pciid in vfs.keys():
 
736
            # This is probably a non SR-IOV PCI device being passed
 
737
            # through. Log that we cannot find details and carry on
 
738
            # processing the device list.
 
739
            syslog.syslog("Could not find VF details for %s for %s, assume it is a non SR-IOV passthrough" % (pciid, vmuuid))
 
740
            continue
 
741
        vfnum, ethdev = vfs[pciid]
 
742
        if mac:
 
743
            # Check MAC really is a MAC
 
744
            if not re.match("^([0-9a-fA-F:]+)$", mac):
 
745
                raise "Unexpected MAC text '%s' for VM %s" % (mac, vmuuid)
 
746
            cmd = "%s link set %s vf %s mac %s" % (ipcmd, ethdev, vfnum, mac)
 
747
            reply.append(cmd)
 
748
            syslog.syslog("Setting VF MAC with '%s' for VM %s" % (cmd, vmuuid))
 
749
            os.system(cmd)
 
750
        if not vlan:
 
751
            # No VLAN may need explicit clearly of previously assigned VLAN
 
752
            vlan = "0"
 
753
        # Check VLAN really is an integer
 
754
        if not re.match("^\d+$", vlan):
 
755
            raise "Unexpected VLAN text '%s' for VM %s" % (vlan, vmuuid)
 
756
        cmd2 = "%s link set %s vf %s vlan %s" % (ipcmd, ethdev, vfnum, vlan)
 
757
        reply.append(cmd2)
 
758
        syslog.syslog("Setting VF VLAN with '%s' for VM %s" % (cmd, vmuuid))
 
759
        os.system(cmd2)
 
760
    return "\n".join(reply)
 
761
 
 
762
def unassign_all(session, args):
 
763
    """Clear all SR-IOV and PCI passthrough devices from a VM."""
 
764
    if not args.has_key("uuid"):
 
765
        raise "No VM UUID specified, please use the 'uuid' argument"
 
766
    vmuuid = args["uuid"]
 
767
    
 
768
    # Update the config
 
769
    _set_vm_assignments(session, vmuuid, {})
 
770
    return ""
 
771
 
 
772
def change_vf_mac(session, args):
 
773
    """Change the MAC address for a VF already assigned to a VM."""
 
774
 
 
775
    if not args.has_key("mac"):
 
776
        raise "Need to specify a new MAC address"
 
777
    mac = args["mac"]
 
778
 
 
779
    if not args.has_key("uuid"):
 
780
        raise "No VM UUID specified, please use the 'uuid' argument"
 
781
    vmuuid = args["uuid"]
 
782
 
 
783
    # Specify the VF by index
 
784
    if not args.has_key("index"):
 
785
        raise "Need to specify the VF index"
 
786
    index = int(args["index"])
 
787
 
 
788
    # Current assignments
 
789
    current = _get_vm_assignments(session, vmuuid)
 
790
 
 
791
    # Update the MAC
 
792
    if not current.has_key(index):
 
793
        raise "VF index %u not found for VM %s" % (index, vmuuid)
 
794
    c = current[index]
 
795
    current[index] = (c[0], c[1], c[2], mac, c[4])
 
796
 
 
797
    # Update the config
 
798
    _set_vm_assignments(session, vmuuid, current)
 
799
 
 
800
    return ""
 
801
 
 
802
def change_vf_vlan(session, args):
 
803
    """Change the VLAN for a VF already assigned to a VM.
 
804
    Use vlan=None to remove VLAN tagging."""
 
805
 
 
806
    if not args.has_key("vlan"):
 
807
        raise "Need to specify a new VLAN"
 
808
    vlan = args["vlan"]
 
809
    if vlan.lower() == "none":
 
810
        vlan = None
 
811
 
 
812
    if not args.has_key("uuid"):
 
813
        raise "No VM UUID specified, please use the 'uuid' argument"
 
814
    vmuuid = args["uuid"]
 
815
 
 
816
    # Specify the VF by index
 
817
    if not args.has_key("index"):
 
818
        raise "Need to specify the VF index"
 
819
    index = int(args["index"])
 
820
 
 
821
    # Current assignments
 
822
    current = _get_vm_assignments(session, vmuuid)
 
823
 
 
824
    # Update the MAC
 
825
    if not current.has_key(index):
 
826
        raise "VF index %u not found for VM %s" % (index, vmuuid)
 
827
    c = current[index]
 
828
    current[index] = (c[0], c[1], c[2], c[3], vlan)
 
829
 
 
830
    # Update the config
 
831
    _set_vm_assignments(session, vmuuid, current)
 
832
 
 
833
    return ""
 
834
 
 
835
if __name__ == "__main__":
 
836
    XenAPIPlugin.dispatch({"enable_iommu":           enable_iommu,
 
837
                           "assign_free_vf":         assign_free_vf,
 
838
                           "show_summary":           show_summary,
 
839
                           "unassign_vf":            unassign_vf,
 
840
                           "assign_free_pci_device": assign_free_pci_device,
 
841
                           "unassign_pci_device":    unassign_pci_device,
 
842
                           "get_vm":                 get_vm,
 
843
                           "prep_for_vm":            prep_for_vm,
 
844
                           "list_pci_devices":       list_pci_devices,
 
845
                           "unassign_all":           unassign_all,
 
846
                           "change_vf_vlan":         change_vf_vlan,
 
847
                           "change_vf_mac":          change_vf_mac})
 
848