9 # Munge import path to insert build location for libvirt mod
10 sys
.path
.insert(0, sys
.argv
[1])
16 def get_libvirt_api_xml_path():
18 args
= ["pkg-config", "--variable", "libvirt_api", "libvirt"]
19 proc
= subprocess
.Popen(args
, stdout
=subprocess
.PIPE
)
20 stdout
, _
= proc
.communicate()
22 sys
.exit(proc
.returncode
)
23 return stdout
.splitlines()[0]
25 # Path to the libvirt API XML file
26 if len(sys
.argv
) >= 3:
29 xml
= get_libvirt_api_xml_path()
31 with
open(xml
, "r") as fp
:
32 tree
= lxml
.etree
.parse(fp
)
42 # Phase 1: Identify all functions and enums in public API
43 set = tree
.xpath('/api/files/file/exports[@type="function"]/@symbol')
45 wantfunctions
.append(n
)
47 set = tree
.xpath('/api/symbols/enum')
49 typ
= n
.attrib
['type']
50 name
= n
.attrib
['name']
51 val
= n
.attrib
['value']
53 if typ
not in enumvals
:
56 # If the value cannot be converted to int, it is reference to
57 # another enum and needs to be sorted out later on
64 enumvals
[typ
][name
] = int(val
)
67 typ
= n
.attrib
['type']
68 name
= n
.attrib
['name']
69 val
= n
.attrib
['value']
71 for v
in enumvals
.values():
78 print("Cannot get a value of enum %s (originally %s)" % (val
, name
))
79 enumvals
[typ
][name
] = val
81 set = tree
.xpath('/api/files/file/exports[@type="enum"]/@symbol')
83 for enumval
in enumvals
.values():
88 if n
.endswith('_LAST') and enum
[n
] == max(enum
.values()):
92 # Phase 2: Identify all classes and methods in the 'libvirt' python module
95 gotfunctions
= { "libvirt": [] }
97 for name
in dir(libvirt
):
100 thing
= getattr(libvirt
, name
)
101 # Special-case libvirtError to deal with python 2.4 difference
102 # in Exception class type reporting.
103 if type(thing
) in (int, long):
104 gotenums
.append(name
)
105 elif type(thing
) == type or name
== "libvirtError":
106 gottypes
.append(name
)
107 gotfunctions
[name
] = []
108 elif callable(thing
):
109 gotfunctions
["libvirt"].append(name
)
113 for enum
in wantenums
:
114 if enum
not in gotenums
:
116 for typ
, enumval
in enumvals
.items():
118 print("FAIL Missing exported enum %s of type %s" % (enum
, typ
))
120 for klassname
in gottypes
:
121 klassobj
= getattr(libvirt
, klassname
)
122 for name
in dir(klassobj
):
125 if name
== 'c_pointer':
127 thing
= getattr(klassobj
, name
)
129 gotfunctions
[klassname
].append(name
)
134 # Phase 3: First cut at mapping of C APIs to python classes + methods
137 for cname
in wantfunctions
:
139 # Some virConnect APIs have stupid names
140 if name
[0:7] == "virNode" and name
[0:13] != "virNodeDevice":
141 name
= "virConnect" + name
[7:]
142 if name
[0:7] == "virConn" and name
[0:10] != "virConnect":
143 name
= "virConnect" + name
[7:]
145 # The typed param APIs are only for internal use
146 if name
[0:14] == "virTypedParams":
149 if name
[0:23] == "virNetworkDHCPLeaseFree":
152 if name
[0:28] == "virDomainStatsRecordListFree":
155 if name
[0:19] == "virDomainFSInfoFree":
158 if name
[0:25] == "virDomainIOThreadInfoFree":
161 if name
[0:22] == "virDomainInterfaceFree":
164 if name
[0:21] == "virDomainListGetStats":
165 name
= "virConnectDomainListGetStats"
167 # These aren't functions, they're callback signatures
168 if name
in ["virConnectAuthCallbackPtr", "virConnectCloseFunc",
169 "virStreamSinkFunc", "virStreamSourceFunc", "virStreamEventCallback",
170 "virEventHandleCallback", "virEventTimeoutCallback", "virFreeCallback",
171 "virStreamSinkHoleFunc", "virStreamSourceHoleFunc", "virStreamSourceSkipFunc"]:
173 if name
[0:21] == "virConnectDomainEvent" and name
[-8:] == "Callback":
175 if name
[0:22] == "virConnectNetworkEvent" and name
[-8:] == "Callback":
177 if (name
.startswith("virConnectStoragePoolEvent") and
178 name
.endswith("Callback")):
180 if (name
.startswith("virConnectNodeDeviceEvent") and
181 name
.endswith("Callback")):
183 if (name
.startswith("virConnectSecretEvent") and
184 name
.endswith("Callback")):
188 # virEvent APIs go into main 'libvirt' namespace not any class
189 if name
[0:8] == "virEvent":
190 if name
[-4:] == "Func":
192 basicklassmap
[name
] = ["libvirt", name
, cname
]
195 # To start with map APIs to classes based on the
196 # naming prefix. Mistakes will be fixed in next
198 for klassname
in gottypes
:
199 klen
= len(klassname
)
200 if name
[0:klen
] == klassname
:
202 if name
not in basicklassmap
:
203 basicklassmap
[name
] = [klassname
, name
[klen
:], cname
]
204 elif len(basicklassmap
[name
]) < klen
:
205 basicklassmap
[name
] = [klassname
, name
[klen
:], cname
]
207 # Anything which can't map to a class goes into the
210 basicklassmap
[name
] = ["libvirt", name
[3:], cname
]
213 # Phase 4: Deal with oh so many special cases in C -> python mapping
216 for name
in sorted(basicklassmap
):
217 klass
= basicklassmap
[name
][0]
218 func
= basicklassmap
[name
][1]
219 cname
= basicklassmap
[name
][2]
221 # The object lifecycle APIs are irrelevant since they're
222 # used inside the object constructors/destructors.
223 if func
in ["Ref", "Free", "New", "GetConnect", "GetDomain"]:
224 if klass
== "virStream" and func
== "New":
231 # All the error handling methods need special handling
232 if klass
== "libvirt":
233 if func
in ["CopyLastError", "DefaultErrorFunc",
234 "ErrorFunc", "FreeError",
235 "SaveLastError", "ResetError"]:
237 elif func
in ["GetLastError", "GetLastErrorMessage",
238 "GetLastErrorCode", "GetLastErrorDomain",
239 "ResetLastError", "Initialize"]:
241 elif func
== "SetErrorFunc":
242 func
= "RegisterErrorHandler"
243 elif klass
== "virConnect":
244 if func
in ["CopyLastError", "SetErrorFunc"]:
246 elif func
in ["GetLastError", "ResetLastError"]:
247 func
= "virConn" + func
249 # Remove 'Get' prefix from most APIs, except those in virConnect
250 # and virDomainSnapshot namespaces which stupidly used a different
251 # convention which we now can't fix without breaking API
252 if func
[0:3] == "Get" and klass
not in ["virConnect", "virDomainSnapshot", "libvirt"]:
253 if func
not in ["GetCPUStats", "GetTime"]:
256 # The object creation and lookup APIs all have to get re-mapped
257 # into the parent class
258 if func
in ["CreateXML", "CreateLinux", "CreateXMLWithFiles",
259 "DefineXML", "CreateXMLFrom", "LookupByUUID",
260 "LookupByUUIDString", "LookupByVolume" "LookupByName",
261 "LookupByID", "LookupByName", "LookupByKey", "LookupByPath",
262 "LookupByMACString", "LookupByUsage", "LookupByVolume",
263 "LookupByTargetPath","LookupSCSIHostByWWN",
264 "Restore", "RestoreFlags",
265 "SaveImageDefineXML", "SaveImageGetXMLDesc", "DefineXMLFlags"]:
266 if klass
!= "virDomain":
267 func
= klass
[3:] + func
269 if klass
== "virDomainSnapshot":
272 elif klass
== "virStorageVol" and func
in ["StorageVolCreateXMLFrom", "StorageVolCreateXML"]:
273 klass
= "virStoragePool"
275 elif func
== "StoragePoolLookupByVolume":
276 klass
= "virStorageVol"
277 elif func
== "StorageVolLookupByName":
278 klass
= "virStoragePool"
282 # The open methods get remapped to primary namespace
283 if klass
== "virConnect" and func
in ["Open", "OpenAuth", "OpenReadOnly"]:
286 # These are inexplicably renamed in the python API
287 if func
== "ListDomains":
288 func
= "ListDomainsID"
289 elif func
== "ListAllNodeDevices":
290 func
= "ListAllDevices"
291 elif func
== "ListNodeDevices":
294 # The virInterfaceChangeXXXX APIs go into virConnect. Stupidly
295 # they have lost their 'interface' prefix in names, but we can't
297 if func
[0:6] == "Change":
300 # Need to special case the snapshot APIs
301 if klass
== "virDomainSnapshot" and func
in ["Current", "ListNames", "Num"]:
303 func
= "snapshot" + func
305 # Names should start with lowercase letter...
306 func
= func
[0:1].lower() + func
[1:]
307 if func
[0:8] == "nWFilter":
308 func
= "nwfilter" + func
[8:]
309 if func
[0:8] == "fSFreeze" or func
[0:6] == "fSThaw" or func
[0:6] == "fSInfo":
310 func
= "fs" + func
[2:]
311 if func
[0:12] == "iOThreadInfo":
312 func
= "ioThreadInfo"
314 if klass
== "virNetwork":
315 func
= func
.replace("dHCP", "DHCP")
317 # ...except when they don't. More stupid naming
318 # decisions we can't fix
323 if func
== "uUIDString":
327 if func
== "xMLDesc":
329 if func
== "mACString":
332 finalklassmap
[name
] = [klass
, func
, cname
]
335 # Phase 5: Validate sure that every C API is mapped to a python API
337 for name
in sorted(finalklassmap
):
338 klass
= finalklassmap
[name
][0]
339 func
= finalklassmap
[name
][1]
341 if func
in gotfunctions
[klass
]:
342 usedfunctions
["%s.%s" % (klass
, func
)] = 1
344 print("PASS %s -> %s.%s" % (name
, klass
, func
))
346 print("FAIL %s -> %s.%s (C API not mapped to python)" % (name
, klass
, func
))
350 # Phase 6: Validate that every python API has a corresponding C API
351 for klass
in gotfunctions
:
352 if klass
== "libvirtError":
354 for func
in sorted(gotfunctions
[klass
]):
355 # These are pure python methods with no C APi
356 if func
in ["connect", "getConnect", "domain", "getDomain",
357 "virEventInvokeFreeCallback",
358 "sparseRecvAll", "sparseSendAll"]:
361 key
= "%s.%s" % (klass
, func
)
362 if not key
in usedfunctions
:
363 print("FAIL %s.%s (Python API not mapped to C)" % (klass
, func
))
367 print("PASS %s.%s" % (klass
, func
))
369 # Phase 7: Validate that all the low level C APIs have binding
370 for name
in sorted(finalklassmap
):
371 cname
= finalklassmap
[name
][2]
374 if pyname
== "virSetErrorFunc":
375 pyname
= "virRegisterErrorHandler"
376 elif pyname
== "virConnectListDomains":
377 pyname
= "virConnectListDomainsID"
379 # These exist in C and exist in python, but we've got
380 # a pure-python impl so don't check them
381 if name
in ["virStreamRecvAll", "virStreamSendAll",
382 "virStreamSparseRecvAll", "virStreamSparseSendAll"]:
386 thing
= getattr(libvirt
.libvirtmod
, pyname
)
387 except AttributeError:
388 print("FAIL libvirt.libvirtmod.%s (C binding does not exist)" % pyname
)