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", "GetNetwork"]:
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", "virDomainCheckpoint", "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", "LookupByPortDev",
264 "Restore", "RestoreFlags",
265 "SaveImageDefineXML", "SaveImageGetXMLDesc", "DefineXMLFlags"]:
266 if klass
!= "virDomain":
267 func
= klass
[3:] + func
269 if klass
in ["virDomainCheckpoint", "virDomainSnapshot"]:
272 elif klass
== "virStorageVol" and func
in ["StorageVolCreateXMLFrom", "StorageVolCreateXML"]:
273 klass
= "virStoragePool"
275 elif klass
== "virNetworkPort":
278 elif func
== "StoragePoolLookupByVolume":
279 klass
= "virStorageVol"
280 elif func
== "StorageVolLookupByName":
281 klass
= "virStoragePool"
285 # The open methods get remapped to primary namespace
286 if klass
== "virConnect" and func
in ["Open", "OpenAuth", "OpenReadOnly"]:
289 # These are inexplicably renamed in the python API
290 if func
== "ListDomains":
291 func
= "ListDomainsID"
292 elif func
== "ListAllNodeDevices":
293 func
= "ListAllDevices"
294 elif func
== "ListNodeDevices":
297 # The virInterfaceChangeXXXX APIs go into virConnect. Stupidly
298 # they have lost their 'interface' prefix in names, but we can't
300 if func
[0:6] == "Change":
303 # Need to special case the checkpoint and snapshot APIs
304 if klass
== "virDomainSnapshot" and func
in ["Current", "ListNames", "Num"]:
306 func
= "snapshot" + func
308 # Names should start with lowercase letter...
309 func
= func
[0:1].lower() + func
[1:]
310 if func
[0:8] == "nWFilter":
311 func
= "nwfilter" + func
[8:]
312 if func
[0:8] == "fSFreeze" or func
[0:6] == "fSThaw" or func
[0:6] == "fSInfo":
313 func
= "fs" + func
[2:]
314 if func
[0:12] == "iOThreadInfo":
315 func
= "ioThreadInfo"
317 if klass
== "virNetwork":
318 func
= func
.replace("dHCP", "DHCP")
320 # ...except when they don't. More stupid naming
321 # decisions we can't fix
326 if func
== "uUIDString":
330 if func
== "xMLDesc":
332 if func
== "mACString":
335 finalklassmap
[name
] = [klass
, func
, cname
]
338 # Phase 5: Validate sure that every C API is mapped to a python API
340 for name
in sorted(finalklassmap
):
341 klass
= finalklassmap
[name
][0]
342 func
= finalklassmap
[name
][1]
344 if func
in gotfunctions
[klass
]:
345 usedfunctions
["%s.%s" % (klass
, func
)] = 1
347 print("PASS %s -> %s.%s" % (name
, klass
, func
))
349 print("FAIL %s -> %s.%s (C API not mapped to python)" % (name
, klass
, func
))
353 # Phase 6: Validate that every python API has a corresponding C API
354 for klass
in gotfunctions
:
355 if klass
== "libvirtError":
357 for func
in sorted(gotfunctions
[klass
]):
358 # These are pure python methods with no C APi
359 if func
in ["connect", "getConnect", "domain", "getDomain",
360 "virEventInvokeFreeCallback",
361 "sparseRecvAll", "sparseSendAll"]:
364 key
= "%s.%s" % (klass
, func
)
365 if not key
in usedfunctions
:
366 print("FAIL %s.%s (Python API not mapped to C)" % (klass
, func
))
370 print("PASS %s.%s" % (klass
, func
))
372 # Phase 7: Validate that all the low level C APIs have binding
373 for name
in sorted(finalklassmap
):
374 cname
= finalklassmap
[name
][2]
377 if pyname
== "virSetErrorFunc":
378 pyname
= "virRegisterErrorHandler"
379 elif pyname
== "virConnectListDomains":
380 pyname
= "virConnectListDomainsID"
382 # These exist in C and exist in python, but we've got
383 # a pure-python impl so don't check them
384 if name
in ["virStreamRecvAll", "virStreamSendAll",
385 "virStreamSparseRecvAll", "virStreamSparseSendAll"]:
389 thing
= getattr(libvirt
.libvirtmod
, pyname
)
390 except AttributeError:
391 print("FAIL libvirt.libvirtmod.%s (C binding does not exist)" % pyname
)