1# VMware vSphere Python SDK
2# Copyright (c) 2008-2018 VMware, Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16## VMOMI support code
17from __future__ import absolute_import
18from __future__ import with_statement # 2.5 only
19
20from six import iteritems
21from six import iterkeys
22from six import itervalues
23from six import text_type
24from six import string_types
25from six import binary_type
26from six import PY3
27from datetime import datetime
28from pyVmomi import Iso8601
29import base64
30import json
31import threading
32if PY3:
33   from functools import cmp_to_key
34
35NoneType = type(None)
36try:
37   from pyVmomi.pyVmomiSettings import allowGetSet
38   _allowGetSet = allowGetSet
39except:
40   _allowGetSet = True
41
42try:
43   from pyVmomi.pyVmomiSettings import allowCapitalizedNames
44   _allowCapitalizedNames = allowCapitalizedNames
45except:
46   _allowCapitalizedNames = True
47
48(F_LINK,
49 F_LINKABLE,
50 F_OPTIONAL,
51 F_SECRET) = [ 1<<x for x in range(4) ]
52
53BASE_VERSION = 'vmodl.version.version0'
54VERSION1     = 'vmodl.version.version1'
55
56XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
57XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
58XMLNS_VMODL_BASE = "urn:vim25"
59
60# The lock ensures that we serialize the lazy loading. In particular, we need
61# to guard against multiple threads loading the same types on the same kind
62# of objects at the same time
63# The lock is protecting the following variables:
64#  _topLevelNames, _*DefMap, and _dependencyMap
65_lazyLock = threading.RLock()
66
67# Also referenced in __init__.py
68_topLevelNames = set()
69
70# Maps to store parameters to create the type for each vmodlName
71_managedDefMap = {}
72_dataDefMap = {}
73_enumDefMap = {}
74
75# Map to store parameters to create the type for each wsdlName
76_wsdlDefMap = {}
77
78# Map that stores the nested classes for a given class
79# if a.b.c and a.b.d are the nested classes of a.b, then _dependencyMap[a.b] = {c,d}
80_dependencyMap = {}
81
82# Map top level names to xml namespaces
83_urnMap = {"vim": XMLNS_VMODL_BASE, "sms": "urn:sms", "pbm": "urn:pbm"}
84
85## Update the dependency map
86#  Note: Must be holding the _lazyLock
87#
88# @param names VmodlName of the type
89def _AddToDependencyMap(names):
90   """ Note: Must be holding the _lazyLock """
91   curName = names[0]
92   _topLevelNames.add(curName)
93   for name in names[1:]:
94      _dependencyMap.setdefault(curName, set()).add(name)
95      curName = ".".join([curName, name])
96
97## Check if a particular name is dependent on another
98#  Note: Must be holding the _lazyLock
99#
100# @param parent Parent Vmodl name
101# @param name Vmodl name to be checked for dependency
102# @return True, if name depends on parent, False otherwise
103def _CheckForDependency(parent, name):
104   """ Note: Must be holding the _lazyLock """
105   if _allowCapitalizedNames:
106      # If the flag is set, check for both capitalized and
107      # uncapitalized form. This is a temporary fix for handling
108      # vim.EsxCLI namespace.
109      # Ex: If we add vim.EsxCLI.vdisk, then
110      # _dependencyMap['vim.EsxCLI'] will have value ['vdisk'].
111      # When we try to check dependency for vdisk, since the flag
112      # is set, we will uncapitalize EsxCLI and this will result
113      # in attribute error
114      dependents = _dependencyMap.get(parent)
115      if not dependents:
116         uncapitalizedParent = UncapitalizeVmodlName(parent)
117         dependents = _dependencyMap.get(uncapitalizedParent)
118
119      if dependents:
120         if name in dependents or Uncapitalize(name) in dependents:
121            return True
122   else:
123      dependents = _dependencyMap.get(parent)
124      if dependents:
125         if name in dependents:
126            return True
127   return False
128
129## Checks for the type definition in all the maps
130# and loads it if it finds the definition
131#
132# @param name vmodl name of the type
133# @return vmodl type
134def _LoadVmodlType(name):
135   isArray = name.endswith("[]")
136   if isArray:
137      name = name[:-2]
138   if _allowCapitalizedNames:
139      name = UncapitalizeVmodlName(name)
140
141   with _lazyLock:
142      for defMap, loadFn in [(_dataDefMap, LoadDataType),
143                            (_managedDefMap, LoadManagedType),
144                            (_enumDefMap, LoadEnumType)]:
145         dic = defMap.get(name)
146         if dic:
147            typ = loadFn(*dic)
148            return isArray and typ.Array or typ
149
150      return None
151
152# In Python 2.4 and earlier, exceptions are old-style classes, so data objects
153# must be old-style too.  For 2.5 and newer, data objects must be new-style
154# classes
155if issubclass(Exception, object):
156   Base = object
157   SetAttr = object.__setattr__
158else:
159   class Base: pass
160   def SetAttr(obj, key, val):
161      obj.__dict__[key] = val
162
163## Simple class to store named attributes
164class Object:
165   ## Constructor
166   def __init__(self, **kwargs):
167      self.__dict__.update(kwargs)
168
169# All properties and methods in vmodl types are created as LazyObject's
170# in VmomiSupport. The attributes in these properties and methods that refer
171# to vmodl types are "type", "result" and "methodResult". If a program tries
172# to access any of these attributes, load the type. The vmodl name of the type
173# can be retrieved by adding name to the attribute that is being accessed
174# Creating a derived class of Object so that programs that want to use just
175# Object are not affected
176class LazyObject(Object):
177
178   def __getattr__(self, attr):
179      with _lazyLock:
180         # Check if another thread already initialized this
181         obj = self.__dict__.get(attr)
182         if obj:
183            return obj
184
185         if attr == "type" or attr == "result" or attr == "methodResult":
186            attrName = attr + "Name"
187            vmodlName = getattr(self, attrName)
188            vmodlType = GetVmodlType(vmodlName)
189            setattr(self, attr, vmodlType)
190            delattr(self, attrName)
191            return vmodlType
192         else:
193            raise AttributeError(attr)
194
195class Link(text_type):
196   def __new__(cls, obj):
197      if isinstance(obj, string_types):
198         return text_type.__new__(cls, obj)
199      elif isinstance(obj, DataObject):
200         if obj.key:
201            return text_type.__new__(cls, obj.key)
202         raise AttributeError("DataObject does not have a key to link")
203      else:
204         raise ValueError
205
206## LazyType to wrap around actual type
207# This is used to intercept attribute accesses of a class
208# and load the appropriate nested classes on-demand
209class LazyType(type):
210
211   def __getattr__(self, attr):
212      if attr.endswith("[]"):
213         searchName = attr[:-2]
214      else:
215         searchName = attr
216
217      with _lazyLock:
218         nestedClasses = _dependencyMap.get(self.__name__, [])
219         if searchName in nestedClasses:
220            return GetVmodlType(self.__name__ + "." +  attr)
221         else:
222            return super(LazyType, self).__getattribute__(attr)
223
224## LazyModule class
225# Used as a placeholder until the actual type is loaded
226# If someone wants to use the type, then it is loaded on-demand
227class LazyModule(object):
228
229   def __init__(self, name):
230      # name is used to save the current context of the object
231      # If it is created because of reference to a.b, name will
232      # be a.b
233      self.name = name
234
235   def __getattr__(self, attr):
236      # If someone tries to introspect the instance of this class
237      # using inspect.isclass(), the function will check if the object
238      # has __bases__ attr. So, throwing an AttributeError to make it work
239      if attr == "__bases__":
240         raise AttributeError
241
242      with _lazyLock:
243         # Check if we have already loaded the class or object
244         # corresponding to this attribute
245         obj = self.__dict__.get(attr)
246         if obj:
247            return obj
248
249         name = ".".join([self.name, attr])
250         # Get the actual vmodlName from the type dictionaries
251         actualName = _GetActualName(name)
252         if actualName:
253            typeObj = GetVmodlType(actualName)
254         else:
255            if _CheckForDependency(self.name, attr):
256               typeObj = LazyModule(name)
257            elif self.name in _urnMap:
258               try:
259                  typeObj = GetWsdlType(_urnMap[self.name], attr)
260               except:
261                  raise AttributeError(attr)
262            else:
263               raise AttributeError(attr)
264         setattr(self, attr, typeObj)
265         return typeObj
266
267   # If the lazy module is representing a data object,
268   # this will get triggered when some code tries to initialize it
269   # Load the actual type and pass the arguments to it's init.
270   def __call__(self, **kwargs):
271      typ = _LoadVmodlType(self.name)
272      if typ:
273         return typ.__call__(**kwargs)
274      else:
275         raise AttributeError("'%s' does not exist" % self.name)
276
277
278class VmomiJSONEncoder(json.JSONEncoder):
279    """
280        Custom JSON encoder to encode vSphere objects.
281
282        When a ManagedObject is encoded, it gains three properties:
283            _vimid is the _moId (ex: 'vm-42')
284            _vimref is the moRef (ex: 'vim.VirtualMachine:vm-42')
285            _vimtype is the class name (ex: 'vim.VirtualMachine')
286
287        When a DataObject is encoded, it gains one property:
288            _vimtype is the class name (ex: 'vim.VirtualMachineQuestionInfo')
289
290        If the dynamicProperty and dynamicType are empty, they are optionally
291            omitted from the results of DataObjects and ManagedObjects
292
293        @example "Explode only the object passed in"
294            data = json.dumps(vm, cls=VmomiJSONEncoder)
295
296        @example "Explode specific objects"
297            data = json.dumps(vm, cls=VmomiJSONEncoder,
298                              explode=[vm, vm.network[0]])
299
300        @example "Explode all virtual machines in a list and their snapshots"
301            data = json.dumps([vm1, vm2], cls=VmomiJSONEncoder,
302                              explode=[templateOf('VirtualMachine'),
303                                       templateOf('VirtualMachineSnapshot')])
304    """
305    def __init__(self, *args, **kwargs):
306        """
307        Consumer must specify what types to explode into full content
308        and what types to leave as a ManagedObjectReference.  If the
309        root object of the encoding is a ManagedObject, it will be
310        added to the explode list automatically.
311
312        A ManagedObject is only exploded once, the first time it is
313        encountered.  All subsequent times it will be a moRef.
314
315        @param explode (list) A list of objects and/or types to
316            expand in the conversion process.  Directly add any
317            objects to the list, or add the type of any object
318            using type(templateOf('VirtualMachine')) for example.
319
320        @param strip_dynamic (bool) Every object normally contains
321            a dynamicProperty list and a dynamicType field even if
322            the contents are [] and None respectively.  These fields
323            clutter the output making it more difficult for a person
324            to read and bloat the size of the result unnecessarily.
325            This option removes them if they are the empty values above.
326            The default is False.
327        """
328        self._done = set()
329        self._first = True
330        self._explode = kwargs.pop('explode', None)
331        self._strip_dynamic = kwargs.pop('strip_dynamic', False)
332        super(VmomiJSONEncoder, self).__init__(*args, **kwargs)
333
334    def _remove_empty_dynamic_if(self, result):
335        if self._strip_dynamic:
336            if 'dynamicProperty' in result and len(result['dynamicProperty']) == 0:
337                result.pop('dynamicProperty')
338            if 'dynamicType' in result and not result['dynamicType']:
339                result.pop('dynamicType')
340        return result
341
342    def default(self, obj):  # pylint: disable=method-hidden
343        if self._first:
344            self._first = False
345            if not self._explode:
346                self._explode = []
347            if isinstance(obj, ManagedObject):
348                self._explode.append(obj)
349        if isinstance(obj, ManagedObject):
350            if self.explode(obj):
351                result = {}
352                result['_vimid'] = obj._moId
353                result['_vimref'] = '{}:{}'.format(obj.__class__.__name__,
354                                                   obj._moId)
355                result['_vimtype'] = obj.__class__.__name__
356                for prop in obj._GetPropertyList():
357                    result[prop.name] = getattr(obj, prop.name)
358                return self._remove_empty_dynamic_if(result)
359            return str(obj).strip("'")  # see FormatObject below
360        if isinstance(obj, DataObject):
361            result = {k:v for k,v in obj.__dict__.items()}
362            result['_vimtype'] = obj.__class__.__name__
363            return self._remove_empty_dynamic_if(result)
364        if isinstance(obj, binary):
365            result = base64.b64encode(obj)
366            if PY3:  # see FormatObject below
367                result = str(result, 'utf-8')
368            return result
369        if isinstance(obj, datetime):
370            return Iso8601.ISO8601Format(obj)
371        if isinstance(obj, UncallableManagedMethod):
372            return obj.name
373        if isinstance(obj, ManagedMethod):
374            return str(obj)  # see FormatObject below
375        if isinstance(obj, type):
376            return obj.__name__
377        super(VmomiJSONEncoder, self).default(obj)
378
379    def explode(self, obj):
380        """ Determine if the object should be exploded. """
381        if obj in self._done:
382            return False
383        result = False
384        for item in self._explode:
385            if hasattr(item, '_moId'):
386                # If it has a _moId it is an instance
387                if obj._moId == item._moId:
388                    result = True
389            else:
390                # If it does not have a _moId it is a template
391                if obj.__class__.__name__ == item.__name__:
392                    result = True
393        if result:
394            self._done.add(obj)
395        return result
396
397
398def templateOf(typestr):
399    """ Returns a class template. """
400    return GetWsdlType(XMLNS_VMODL_BASE, typestr)
401
402## Format a python VMOMI object
403#
404# @param val the object
405# @param info the field
406# @param indent the level of indentation
407# @return the formatted string
408def FormatObject(val, info=Object(name="", type=object, flags=0), indent=0):
409   start = indent * "   " + (info.name and "%s = " % info.name or "")
410   if val == None:
411      result = "<unset>"
412   elif isinstance(val, DataObject):
413      if info.flags & F_LINK:
414         result = "<%s:%s>" % (val.__class__.__name__, val.key)
415      else:
416         result = "(%s) {\n%s\n%s}" % (val.__class__.__name__,
417            ',\n'.join([FormatObject(getattr(val, prop.name), prop, indent+1)
418                        for prop in val._GetPropertyList()]), indent * "   ")
419   elif isinstance(val, ManagedObject):
420      if val._serverGuid is None:
421         result = "'%s:%s'" % (val.__class__.__name__, val._moId)
422      else:
423         result = "'%s:%s:%s'" % (val.__class__.__name__, val._serverGuid,
424                                  val._moId)
425   elif isinstance(val, list):
426      itemType = getattr(val, 'Item', getattr(info.type, 'Item', object))
427      if val:
428         item = Object(name="", type=itemType, flags=info.flags)
429         result = "(%s) [\n%s\n%s]" % (itemType.__name__,
430            ',\n'.join([FormatObject(obj, item, indent+1) for obj in val]),
431            indent * "   ")
432      else:
433         result = "(%s) []" % itemType.__name__
434   elif isinstance(val, type):
435      result = val.__name__
436   elif isinstance(val, UncallableManagedMethod):
437      result = val.name
438   elif isinstance(val, ManagedMethod):
439      result = '%s.%s' % (val.info.type.__name__, val.info.name)
440   elif isinstance(val, bool):
441      result = val and "true" or "false"
442   elif isinstance(val, datetime):
443      result = Iso8601.ISO8601Format(val)
444   elif isinstance(val, binary):
445      result = base64.b64encode(val)
446      if PY3:
447         # In python3 the bytes result after the base64 encoding has a
448         # leading 'b' which causes error when we use it to construct the
449         # soap message. Workaround the issue by converting the result to
450         # string. Since the result of base64 encoding contains only subset
451         # of ASCII chars, converting to string will not change the value.
452         result = str(result, 'utf-8')
453   else:
454      result = repr(val)
455   return start + result
456
457## Lookup a property for a given object type
458#
459# @param type the type
460# @param name the name of the property
461def GetPropertyInfo(type, name):
462   try:
463      while name not in type._propInfo:
464         type = type.__bases__[0]
465      else:
466         return type._propInfo[name]
467   except Exception:
468      raise AttributeError(name)
469
470## VMOMI Managed Object class
471class ManagedObject(object):
472   _wsdlName = "ManagedObject"
473   _propInfo = {}
474   _propList = []
475   _methodInfo = {}
476   _version = BASE_VERSION
477
478   ## Constructor
479   #
480   # @param[in] self self
481   # @param[in] moId The ID of this managed object
482   # @param[in] stub The stub adapter, if this is a client stub object
483   def __init__(self, moId, stub=None, serverGuid=None):
484      object.__setattr__(self, "_moId", moId)
485      object.__setattr__(self, "_stub", stub)
486      object.__setattr__(self, "_serverGuid", serverGuid)
487
488   ## Invoke a managed method
489   #
490   # @param info method info
491   # @param self self
492   # @param ... arguments
493   def _InvokeMethod(info, self, *posargs, **kwargs):
494      if len(posargs) > len(info.params):
495         s = "s"*(len(info.params)!=1)
496         raise TypeError("%s() takes at most %d argument%s (%d given)" %
497            (Capitalize(info.name),  len(info.params), s, len(posargs)))
498      args = list(posargs) + [None] * (len(info.params) - len(posargs))
499      if len(kwargs) > 0:
500         paramNames = [param.name for param in info.params]
501         for (k, v) in list(kwargs.items()):
502            try:
503               idx = paramNames.index(k)
504            except ValueError:
505               raise TypeError("%s() got an unexpected keyword argument '%s'" %
506                              (Capitalize(info.name),  k))
507            if idx < len(posargs):
508               raise TypeError("%s() got multiple values for keyword argument '%s'" %
509                              (Capitalize(info.name),  k))
510            args[idx] = v
511      list(map(CheckField, info.params, args))
512      return self._stub.InvokeMethod(self, info, args)
513   _InvokeMethod = staticmethod(_InvokeMethod)
514
515   ## Invoke a managed property accessor
516   #
517   # @param info property info
518   # @param self self
519   def _InvokeAccessor(info, self):
520      return self._stub.InvokeAccessor(self, info)
521   _InvokeAccessor = staticmethod(_InvokeAccessor)
522
523   ## Get the ID of a managed object
524   def _GetMoId(self):
525      return self._moId
526
527   ## Get the serverGuid of a managed object
528   def _GetServerGuid(self):
529      return self._serverGuid
530
531   ## Get the stub of a managed object
532   def _GetStub(self):
533      return self._stub
534
535   ## Get a list of all properties of this type and base types
536   #
537   # @param cls The managed object type
538   def _GetPropertyList(cls, includeBaseClassProps=True):
539      if not includeBaseClassProps:
540         return cls._propList
541      prop = {}
542      result = []
543      while cls != ManagedObject:
544         # Iterate through props, add info for prop not found in derived class
545         result = [info for info in cls._propList
546                   if prop.setdefault(info.name, cls) == cls] + result
547         cls = cls.__bases__[0]
548      return result
549   _GetPropertyList = classmethod(_GetPropertyList)
550
551   ## Get a list of all methods of this type and base types
552   #
553   # @param cls The managed object type
554   def _GetMethodList(cls):
555      meth = {}
556      result = []
557      while cls != ManagedObject:
558         # Iterate through methods, add info for method not found in derived class
559         result = [info for info in list(cls._methodInfo.values())
560                   if meth.setdefault(info.name, cls) == cls] + result
561         cls = cls.__bases__[0]
562      return result
563   _GetMethodList = classmethod(_GetMethodList)
564
565   ## Lookup a method for a given managed object type
566   #
567   # @param type the type
568   # @param name the name of the property
569   def _GetMethodInfo(type, name):
570      while hasattr(type, "_methodInfo"):
571         try:
572            return type._methodInfo[name]
573         except KeyError:
574            type = type.__bases__[0]
575      raise AttributeError(name)
576   _GetMethodInfo = classmethod(_GetMethodInfo)
577
578   def __setattr__(self,*args):
579      if self._stub is not None:
580         raise Exception("Managed object attributes are read-only")
581      else:
582         object.__setattr__(self,*args)
583   __delattr__ = __setattr__
584
585   if _allowGetSet == True:
586      def __getattr__(self, name):
587         if name.startswith("Get"):
588            return lambda : getattr(self, name[3].lower() + name[4:])
589         elif name.startswith("Set"):
590            return lambda val: setattr(self, name[3].lower() + name[4:], val)
591         raise AttributeError(name)
592
593   ## The equality test of ManagedObject is for client side only and might
594   #  not be appropriate for server side objects. The server side object has
595   #  to override this function if it has a different equality logic
596   def __eq__(self, other):
597      if other is None:
598         return False
599      else:
600         return self._moId == other._moId and \
601                self.__class__ == other.__class__ and \
602                self._serverGuid == other._serverGuid
603
604   def __ne__(self, other):
605      return not(self == other)
606
607   def __hash__(self):
608      return str(self).__hash__()
609
610   __str__ = __repr__ = FormatObject
611   _GetPropertyInfo = classmethod(GetPropertyInfo)
612
613## VMOMI Data Object class
614class DataObject(Base):
615   _wsdlName = "DataObject"
616   _propInfo = {}
617   _propList = []
618   _version = BASE_VERSION
619
620   ## Constructor
621   #
622   # @param info property info
623   # @param ... keyword arguments indicate properties
624   def __init__(self, **kwargs):
625      for info in self._GetPropertyList():
626         if issubclass(info.type, list):
627            SetAttr(self, info.name, info.type())
628         elif info.flags & F_OPTIONAL:
629            SetAttr(self, info.name, None)
630         elif info.type is bool:
631            SetAttr(self, info.name, False)
632         elif issubclass(info.type, Enum):
633            SetAttr(self, info.name, None)
634         elif issubclass(info.type, str):
635            SetAttr(self, info.name, "")
636         elif info.type is long or \
637            issubclass(info.type, int) or \
638            issubclass(info.type, float):
639            # Take care of byte, short, int, long, float and double
640            SetAttr(self, info.name, info.type(0))
641         else:
642            SetAttr(self, info.name, None)
643      for (k, v) in list(kwargs.items()):
644         setattr(self, k, v)
645
646   ## Get a list of all properties of this type and base types
647   #
648   # @param cls the data object type
649   def _GetPropertyList(cls, includeBaseClassProps=True):
650      if not includeBaseClassProps:
651         return cls._propList
652      prop = {}
653      result = []
654      while cls != DataObject:
655         # Iterate through props, add info for prop not found in derived class
656         result = [info for info in cls._propList
657                   if prop.setdefault(info.name, cls) == cls] + result
658         cls = cls.__bases__[0]
659      return result
660   _GetPropertyList = classmethod(_GetPropertyList)
661
662   def __setattr__(self, name, val):
663      CheckField(self._GetPropertyInfo(name), val)
664      SetAttr(self, name, val)
665
666   if _allowGetSet == True:
667      def __getattr__(self, name):
668         if name.startswith("Get"):
669            return lambda : getattr(self, name[3].lower() + name[4:])
670         elif name.startswith("Set"):
671            return lambda val: setattr(self, name[3].lower() + name[4:], val)
672         raise AttributeError(name)
673
674   _GetPropertyInfo = classmethod(GetPropertyInfo)
675   __str__ = __repr__ = FormatObject
676
677## Base class for enum types
678class Enum(str): pass
679
680## Base class for array types
681class Array(list):
682   __str__ = __repr__ = FormatObject
683
684## Class for curried function objects
685#
686# Instances of this class are curried python function objects.
687# If g = Curry(f, a1,...,an), then g(b1,...,bm) = f(a1,...,an, b1,...,bm)
688class Curry(object):
689   ## Constructor
690   #
691   # @param self self
692   # @param f the function object
693   # @param args arguments to fix
694   def __init__(self, f, *args):
695      self.f = f
696      self.args = args
697
698   def __call__(self, *args, **kwargs):
699      args = self.args + args
700      return self.f(*args, **kwargs)
701
702   def __get__(self, obj, type):
703      if obj:
704         # curried methods will receive 'self' *after* any fixed arguments
705         return lambda *args, **kwargs: \
706                           self.f(*(self.args + (obj,) + args), **kwargs)
707      return self
708
709## Class for managed object methods
710class ManagedMethod(Curry):
711   ## Constructor
712   #
713   # @param self self
714   # @param info method info
715   def __init__(self, info):
716      Curry.__init__(self, ManagedObject._InvokeMethod, info)
717      self.info = info
718
719# Method used to represent any unknown wsdl method returned by server response.
720# Server may return unknown method name due to server defects or newer version.
721class UncallableManagedMethod(ManagedMethod):
722   def __init__(self, name):
723      self.name = name
724
725   def __call__(self, *args, **kwargs):
726      raise Exception("Managed method {} is not available".format(self.name))
727
728## Create the vmodl.MethodFault type
729#
730# This type must be generated dynamically because it inherits from
731# vmodl.DynamicData, which is created dynamically by the emitted code.
732#
733# @return the new type
734def CreateAndLoadMethodFaultType():
735   with _lazyLock:
736      props = [["msg", "string", BASE_VERSION, F_OPTIONAL],
737               ["faultCause", "vmodl.MethodFault", "vmodl.version.version1", F_OPTIONAL],
738               ["faultMessage", "vmodl.LocalizableMessage[]", "vmodl.version.version1", F_OPTIONAL]]
739      propInfo = {}
740      propList = [ LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3])
741                   for p in props ]
742      dic = {"_wsdlName" : "MethodFault", "_propInfo" : propInfo,
743             "_propList" : propList, "_version" : BASE_VERSION}
744      for info in propList:
745         propInfo[info.name] = info
746      name = "vmodl.MethodFault"
747      CreateDataType("vmodl.MethodFault", "MethodFault", "vmodl.DynamicData", BASE_VERSION, props)
748      return _AddType(type(Exception)(name,
749                      (GetWsdlType(XMLNS_VMODL_BASE, "DynamicData"), Exception),
750                      dic))
751
752# If the name of nested class of vmodl type is same as one of the nested classes
753# of its parent, then we have to replace it. Else it won't be possible to intercept
754# it with LazyType class
755# @param vmodl type
756# @param parent of the vmodl type
757# @return vmodl type
758def _CheckNestedClasses(typ, parent):
759   with _lazyLock:
760      vmodlName = typ.__name__
761      nestedClasses = _dependencyMap.get(vmodlName, [])
762      for nestedClass in nestedClasses:
763         if hasattr(parent, nestedClass):
764            setattr(typ, nestedClass, GetVmodlType(vmodlName + "." + nestedClass))
765      return typ
766
767## Create and Load a data object type at once
768#
769# @param vmodlName the VMODL name of the type
770# @param wsdlName the WSDL name of the type
771# @param parent the VMODL name of the parent type
772# @param version the version of the type
773# @param props properties of the type
774# @return vmodl type
775def CreateAndLoadDataType(vmodlName, wsdlName, parent, version, props):
776   CreateDataType(vmodlName, wsdlName, parent, version, props)
777   return LoadDataType(vmodlName, wsdlName, parent, version, props)
778
779## Create a data object type
780#
781# @param vmodlName the VMODL name of the type
782# @param wsdlName the WSDL name of the type
783# @param parent the VMODL name of the parent type
784# @param version the version of the type
785# @param props properties of the type
786def CreateDataType(vmodlName, wsdlName, parent, version, props):
787   with _lazyLock:
788      dic = [vmodlName, wsdlName, parent, version, props]
789      names = vmodlName.split(".")
790      if _allowCapitalizedNames:
791         vmodlName = ".".join(name[0].lower() + name[1:] for name in names)
792      _AddToDependencyMap(names)
793      typeNs = GetWsdlNamespace(version)
794
795      _dataDefMap[vmodlName] = dic
796      _wsdlDefMap[(typeNs, wsdlName)] = dic
797      _wsdlTypeMapNSs.add(typeNs)
798
799## Load a data object type
800# This function also loads the parent of the type if it's not loaded yet
801#
802# @param vmodlName the VMODL name of the type
803# @param wsdlName the WSDL name of the type
804# @param parent the VMODL name of the parent type
805# @param version the version of the type
806# @param props properties of the type
807# @return the new data object type
808def LoadDataType(vmodlName, wsdlName, parent, version, props):
809   with _lazyLock:
810      # Empty lists are saved as None in globals maps as it is much more memory
811      # efficient. PythonStubEmitter files emit empty lists as None.
812      if props is None:
813         props = []
814      propInfo = {}
815      propList = []
816      for p in props:
817         # DataObject Property does not contain the privilege for emitted types.
818         # However, DynamicTypeConstructor from DynamicTypeManagerHelper.py creates
819         # DataTypes with properties containing privilege id.
820         name, typeName, propVersion, flags = p[:4]
821         if flags & F_LINK:
822            if typeName.endswith("[]"):
823               linkType = "Link[]"
824            else:
825               linkType = "Link"
826            obj = LazyObject(name=name, typeName=linkType, version=propVersion,
827                             flags=flags, expectedType=typeName)
828         else:
829            obj = LazyObject(name=name, typeName=typeName, version=propVersion,
830                             flags=flags)
831         propList.append(obj)
832      dic = {"_wsdlName" : wsdlName, "_propInfo" : propInfo,
833             "_propList" : propList, "_version" : version}
834      for info in propList:
835         propInfo[info.name] = info
836      name = vmodlName
837      parent = GetVmodlType(parent)
838      result = _AddType(LazyType(name, (parent,), dic))
839
840      # MethodFault and RuntimeFault are builtin types, but MethodFault extends
841      # DynamicData, which is (erroneously?) an emitted type, so we can't create
842      # MethodFault and RuntimeFault until we have loaded DynamicData
843      if wsdlName == "DynamicData":
844         CreateAndLoadMethodFaultType()
845         CreateAndLoadDataType("vmodl.RuntimeFault", "RuntimeFault",
846                        "vmodl.MethodFault", BASE_VERSION, [])
847         # Strictly speaking LocalizedMethodFault is not a data object type
848         # (it can't be used in VMODL) But it can be treated as a data object for
849         # (de)serialization purpose
850         CreateAndLoadDataType("vmodl.LocalizedMethodFault", "LocalizedMethodFault",
851                        "vmodl.MethodFault", BASE_VERSION,
852                        [("fault", "vmodl.MethodFault", BASE_VERSION, 0),
853                         ("localizedMessage", "string", BASE_VERSION, F_OPTIONAL),
854                        ])
855
856      return _CheckNestedClasses(result, parent)
857
858## Create and Load a managed object type at once
859#
860# @param vmodlName the VMODL name of the type
861# @param wsdlName the WSDL name of the type
862# @param parent the VMODL name of the parent type
863# @param version the version of the type
864# @param props properties of the type
865# @param methods methods of the type
866# @return vmodl type
867def CreateAndLoadManagedType(vmodlName, wsdlName, parent, version, props, methods):
868   CreateManagedType(vmodlName, wsdlName, parent, version, props, methods)
869   return LoadManagedType(vmodlName, wsdlName, parent, version, props, methods)
870
871## Create a managed object type
872#
873# @param vmodlName the VMODL name of the type
874# @param wsdlName the WSDL name of the type
875# @param parent the VMODL name of the parent type
876# @param version the version of the type
877# @param props properties of the type
878# @param methods methods of the type
879def CreateManagedType(vmodlName, wsdlName, parent, version, props, methods):
880   with _lazyLock:
881      dic = [vmodlName, wsdlName, parent, version, props, methods]
882      names = vmodlName.split(".")
883      if _allowCapitalizedNames:
884         vmodlName = ".".join(name[0].lower() + name[1:] for name in names)
885
886      _AddToDependencyMap(names)
887      typeNs = GetWsdlNamespace(version)
888
889      if methods:
890         for meth in methods:
891            _SetWsdlMethod(typeNs, meth[1], dic)
892
893      _managedDefMap[vmodlName] = dic
894      _wsdlDefMap[(typeNs, wsdlName)] = dic
895      _wsdlTypeMapNSs.add(typeNs)
896
897## Load a managed object type
898# This function also loads the parent of the type if it's not loaded yet
899#
900# @param vmodlName the VMODL name of the type
901# @param wsdlName the WSDL name of the type
902# @param parent the VMODL name of the parent type
903# @param version the version of the type
904# @param props properties of the type
905# @param methods methods of the type
906# @return the new managed object type
907def LoadManagedType(vmodlName, wsdlName, parent, version, props, methods):
908   with _lazyLock:
909      # Empty lists are saved as None in globals maps as it is much more memory
910      # efficient. PythonStubEmitter files emit empty lists as None.
911      if props is None:
912         props = []
913      if methods is None:
914         methods = []
915      parent = GetVmodlType(parent)
916      propInfo = {}
917      methodInfo = {}
918      propList = [LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3],
919                  privId=p[4]) for p in props]
920      dic = {"_wsdlName" : wsdlName, "_propInfo" : propInfo,
921             "_propList" : propList,
922             "_methodInfo" : methodInfo, "_version" : version}
923      for info in propList:
924         propInfo[info.name] = info
925         getter = Curry(ManagedObject._InvokeAccessor, info)
926         dic[info.name] = property(getter)
927      for (mVmodl, mWsdl, mVersion, mParams, mResult, mPrivilege, mFaults) in methods:
928         if mFaults is None:
929            mFaults = []
930         mName = Capitalize(mVmodl)
931         isTask = False
932         if mName.endswith("_Task"):
933            mName = mName[:-5]
934            isTask = True
935         params = tuple([LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3],
936                        privId=p[4]) for p in mParams])
937         info = LazyObject(name=mName, typeName=vmodlName, wsdlName=mWsdl,
938                       version=mVersion, params=params, isTask=isTask,
939                       resultFlags=mResult[0], resultName=mResult[1],
940                       methodResultName=mResult[2], privId=mPrivilege, faults=mFaults)
941         methodInfo[mName] = info
942         mm = ManagedMethod(info)
943         ns = GetWsdlNamespace(info.version)
944         method = _SetWsdlMethod(ns, info.wsdlName, mm)
945         if method != mm:
946            raise RuntimeError(
947               "Duplicate wsdl method %s %s (new class %s vs existing %s)" % \
948               (ns, info.wsdlName, mm.info.type, method.info.type))
949         dic[mWsdl] = mm
950         dic[mName] = mm
951      name = vmodlName
952      result = _AddType(LazyType(name, (parent,) , dic))
953
954      return _CheckNestedClasses(result, parent)
955
956## Create an enum type
957#
958# @param vmodlName the VMODL name of the type
959# @param wsdlName the WSDL name of the type
960# @param version the version of the type
961# @param values enum values
962# @return vmodl type
963def CreateAndLoadEnumType(vmodlName, wsdlName, version, values):
964   CreateEnumType(vmodlName, wsdlName, version, values)
965   return LoadEnumType(vmodlName, wsdlName, version, values)
966
967## Create an enum type
968#
969# @param vmodlName the VMODL name of the type
970# @param wsdlName the WSDL name of the type
971# @param version the version of the type
972# @param values enum values
973def CreateEnumType(vmodlName, wsdlName, version, values):
974   with _lazyLock:
975      dic = [vmodlName, wsdlName, version, values]
976      names = vmodlName.split(".")
977      if _allowCapitalizedNames:
978         vmodlName = ".".join(name[0].lower() + name[1:] for name in names)
979
980      _AddToDependencyMap(names)
981      typeNs = GetWsdlNamespace(version)
982
983      _enumDefMap[vmodlName] = dic
984      _wsdlDefMap[(typeNs, wsdlName)] = dic
985      _wsdlTypeMapNSs.add(typeNs)
986
987## Load an enum type
988#
989# @param vmodlName the VMODL name of the type
990# @param wsdlName the WSDL name of the type
991# @param version the version of the type
992# @param values enum values
993# @return the new enum type
994def LoadEnumType(vmodlName, wsdlName, version, values):
995   with _lazyLock:
996      name = vmodlName
997      # Enum type cannot have nested classes. So, creating normal type
998      # instead of LazyType
999      result = type(name, (Enum,),
1000         {"_wsdlName" : wsdlName, "_version" : version})
1001      result.values = map(result, values)
1002      for value in result.values:
1003         setattr(result, value, value)
1004      return _AddType(result)
1005
1006## Create an array type
1007#
1008# @param itemType the item type
1009# @return the new array type
1010def CreateArrayType(itemType):
1011   return type("%s[]" % itemType.__name__, (Array,), {'Item' : itemType})
1012
1013## Add a new type to the type maps, create array constructors
1014#  Note: Must be holding the _lazyLock, or in main init path
1015#
1016# @param type the type object
1017# @return type
1018def _AddType(type):
1019   """ Note: Must be holding the _lazyLock, or in main init path """
1020   type.Array = CreateArrayType(type)
1021
1022   typeNS = GetWsdlNamespace(type._version)
1023   newType = _SetWsdlType(typeNS, type._wsdlName, type)
1024   if newType != type:
1025      raise RuntimeError("Duplicate wsdl type %s (already in typemap)" % (type._wsdlName))
1026
1027   return type
1028
1029## Check that a value matches a given type, and annotate if neccesary
1030#
1031# @param info object containing of expected type
1032# @param val object to check
1033# @throw TypeError if the value does not match the type
1034def CheckField(info, val):
1035   with _lazyLock:
1036      valType = Type(val)
1037      if val is None or (isinstance(val, list) and len(val) == 0):
1038         # If type of the property is an Any. We should allow this to have
1039         # unset items
1040         if not (info.flags & F_OPTIONAL) and info.type is not object:
1041            raise TypeError('Required field "%s" not provided (not @optional)' % info.name)
1042         return
1043      elif info.type is object:
1044         try:
1045            GetQualifiedWsdlName(valType)
1046            return
1047         except KeyError:
1048            raise TypeError('Unknown type for %s' % info.type.__name__)
1049      elif isinstance(val, info.type):
1050         return
1051      elif issubclass(info.type, list):
1052         # Checking the values of VMOMI array types is surprisingly complicated....
1053         if isinstance(val, Array):
1054            # 1. We've got a PyVmomi Array object, which is effectively a typed list;
1055            # verify that the type of the Array is a subclass of the expected type.
1056            if issubclass(valType.Item, info.type.Item):
1057               return
1058            elif info.flags & F_LINK:
1059               # Allow objects of expected type to be assigned to links
1060               if issubclass(valType, GetVmodlType(info.expectedType)):
1061                  return
1062         elif val:
1063            # 2. We've got a non-empty Python list object, which is untyped;
1064            # walk the list and make sure that each element is a subclass
1065            # of the expected type.
1066
1067            # Masking out F_OPTIONAL part of flags since we are checking for
1068            # each element of the list
1069            flags = info.flags & (F_LINKABLE | F_LINK)
1070            if flags & F_LINK:
1071               if info.expectedType.endswith('[]'):
1072                  expectedType = info.expectedType[:-2]
1073               else:
1074                  expectedType = info.expectedType
1075               itemInfo = Object(type=info.type.Item, name=info.name, flags=flags,
1076                                 expectedType=expectedType)
1077            else:
1078               itemInfo = Object(type=info.type.Item, name=info.name, flags=flags)
1079            for it in val:
1080               CheckField(itemInfo, it)
1081            return
1082         else:
1083            # 3. We've got None or an empty Python list object;
1084            # no checking required, since the result will be an empty array.
1085            return
1086      elif info.type is type and valType is type(Exception) \
1087        or issubclass(info.type, int) and issubclass(valType, int) \
1088        or issubclass(info.type, long) and (issubclass(valType, int) or \
1089                                            issubclass(valType, long)) \
1090        or issubclass(info.type, float) and issubclass(valType, float) \
1091        or issubclass(info.type, string_types) and issubclass(valType, string_types):
1092         return
1093      elif issubclass(info.type, Link):
1094         # Allow object of expected type to be assigned to link
1095         if issubclass(valType, GetVmodlType(info.expectedType)):
1096            return
1097      raise TypeError('For "%s" expected type %s, but got %s'
1098                    % (info.name, info.type.__name__, valType.__name__))
1099
1100## Finalize a created type
1101#
1102# @param type a created type
1103def FinalizeType(type):
1104   if issubclass(type, DataObject):
1105      for info in type._propList:
1106         info.type = GetVmodlType(info.type)
1107   elif issubclass(type, ManagedObject):
1108      for info in list(type._propInfo.values()):
1109         info.type = GetVmodlType(info.type)
1110      for info in list(type._methodInfo.values()):
1111         info.result = GetVmodlType(info.result)
1112         info.methodResult = GetVmodlType(info.methodResult)
1113         info.type = GetVmodlType(info.type)
1114         for param in info.params:
1115            param.type = GetVmodlType(param.type)
1116
1117## Get the type of an object, for both new and old-style classes
1118def Type(obj):
1119   try:
1120      return obj.__class__
1121   except AttributeError:
1122      return type(obj)
1123
1124## Set a WSDL type with wsdl namespace and wsdl name
1125#  Internal to VmomiSupport
1126#
1127#  Note: Must be holding the _lazyLock, or in main init path
1128def _SetWsdlType(ns, wsdlName, typ):
1129   """
1130   Set a WSDL type with wsdl namespace and wsdl name.
1131   Returns added type / existing type if (ns, wsdlName) already in the map
1132
1133   Note: Must be holding the _lazyLock, or in main init path
1134   """
1135   return _wsdlTypeMap.setdefault((ns, wsdlName), typ)
1136
1137## Lookup a WSDL type from wsdl namespace and wsdl name
1138# @param ns XML namespace
1139# @param name wsdl name
1140# @return type if found else throws KeyError
1141def GetWsdlType(ns, name):
1142   if ns is None or name is None:
1143      raise KeyError("{0} {1}".format(ns, name))
1144
1145   with _lazyLock:
1146      # Check if the type is loaded in the map
1147      typ = _wsdlTypeMap.get( (ns, name) )
1148      if typ:
1149         return typ
1150      # It is an array type, get the actual type and return the array
1151      elif name.startswith("ArrayOf"):
1152         try:
1153            return GetWsdlType(ns, name[7:]).Array
1154         except KeyError:
1155            raise KeyError("{0} {1}".format(ns, name))
1156      else:
1157         # Type is not loaded yet, load it
1158         typ = _LoadVmodlType(_wsdlDefMap[(ns, name)][0])
1159         if typ:
1160            return typ
1161
1162      raise KeyError("{0} {1}".format(ns, name))
1163
1164
1165class UnknownWsdlTypeError(KeyError):
1166    # NOTE (hartsock): KeyError is extended here since most logic will be
1167    # looking for the KeyError type. I do want to distinguish malformed WSDL
1168    # errors as a separate classification of error for easier bug reports.
1169    pass
1170
1171## Guess the type from wsdlname with no ns
1172#  WARNING! This should not be used in general, as there is no guarantee for
1173#  the correctness of the guessing type
1174# @param name wsdl name
1175# @return type if found in any one of the name spaces else throws KeyError
1176def GuessWsdlType(name):
1177   with _lazyLock:
1178      # Some types may exist in multiple namespaces, and returning
1179      # the wrong one will cause a deserialization error.
1180      # Since in python3 the order of entries in set is not deterministic,
1181      # we will try to get the type from vim25 namespace first.
1182      try:
1183         return GetWsdlType(XMLNS_VMODL_BASE, name)
1184      except KeyError:
1185         pass
1186
1187      for ns in _wsdlTypeMapNSs:
1188         try:
1189            return GetWsdlType(ns, name)
1190         except KeyError:
1191             pass
1192      raise UnknownWsdlTypeError(name)
1193
1194## Return a map that contains all the wsdl types
1195# This function is rarely used
1196# By calling GetWsdlType on all wsdl names, we will
1197# make sure that the types are loaded before returning
1198# the iterator
1199# @return iterator to the wsdl type map
1200def GetWsdlTypes():
1201   with _lazyLock:
1202      for ns, name in _wsdlDefMap:
1203         GetWsdlType(ns, name)
1204      return itervalues(_wsdlTypeMap)
1205
1206## Get the qualified XML schema name (ns, name) of a type
1207def GetQualifiedWsdlName(type):
1208   with _lazyLock:
1209      wsdlNSAndName = _wsdlNameMap.get(type)
1210      if wsdlNSAndName:
1211         return wsdlNSAndName
1212      else:
1213         if issubclass(type, list):
1214            ns = GetWsdlNamespace(type.Item._version)
1215            return (ns, "ArrayOf" + Capitalize(type.Item._wsdlName))
1216         else:
1217            ns = GetWsdlNamespace(type._version)
1218            return (ns, type._wsdlName)
1219
1220## Get the WSDL of a type
1221def GetWsdlName(type):
1222   return GetQualifiedWsdlName(type)[-1]
1223
1224## Capitalize a string
1225def Capitalize(str):
1226   if str:
1227      return str[0].upper() + str[1:]
1228   return str
1229
1230## Uncapitalize a string
1231def Uncapitalize(str):
1232   if str:
1233      return str[0].lower() + str[1:]
1234   return str
1235
1236## To uncapitalize the entire vmodl name
1237# pyVmomi used to map Java package names to capitalized Python module names,
1238# but now maps the Java package names unchanged to Python module names.
1239# This function is needed to support the legacy name mapping.
1240def UncapitalizeVmodlName(str):
1241   if str:
1242      return ".".join(name[0].lower() + name[1:] for name in str.split("."))
1243   return str
1244
1245## Add a parent version
1246def AddVersionParent(version, parent):
1247   parentMap[version].add(parent)
1248
1249def GetVersionProps(version):
1250   """Get version properties
1251
1252   This function is a fixed version of GetVersion().
1253   """
1254
1255   ns = nsMap[version]
1256   versionId = versionIdMap[version]
1257   isLegacy = versionMap.get(ns) == version
1258   serviceNs =  serviceNsMap[version]
1259   return ns, versionId, isLegacy, serviceNs
1260
1261
1262## Get version namespace from version
1263def GetVersionNamespace(version):
1264   """ Get version namespace from version """
1265   ns = nsMap[version]
1266   if not ns:
1267      ns = serviceNsMap[version]
1268   versionId = versionIdMap[version]
1269   if not versionId:
1270      namespace = ns
1271   else:
1272      namespace = '%s/%s' % (ns, versionId)
1273   return namespace
1274
1275## Get version from the version uri
1276def GetVersionFromVersionUri(version):
1277   return versionMap[version.rsplit(":", 1)[-1]]
1278
1279## Get wsdl namespace from version
1280def GetWsdlNamespace(version):
1281   """ Get wsdl namespace from version """
1282   return "urn:" + serviceNsMap[version]
1283
1284## Get an iterable with all version parents
1285def GetVersionParents(version):
1286   return parentMap[version]
1287
1288## Get all the versions for the service with specified namespace (partially) ordered
1289## by compatibility (i.e. any version in the list that is compatible with some version
1290## v in the list will preceed v)
1291# @param namespace XML namespace identifying a service
1292# @return returns all the versions for the service with specified namespace (partially)
1293# ordered by compatibility
1294#
1295# NOTE: For this function, we use 'namespace' as a representation of 'service'.  While
1296#       this works for most services, for compatibility reasons, the core and query
1297#       services share the 'vim25' namespace with the vim service.  Fortunately, this
1298#       shouldn't be an issue in practice, as the implementations of the vim
1299#       service (vpxd and hostd) don't currently advertise that they support any
1300#       versions of the core or query services, and we don't expect that they ever will.
1301#       This function assumes that all other namespaces identify a unique service.
1302def GetServiceVersions(namespace):
1303   """
1304   Get all the versions for the service with specified namespace (partially) ordered
1305   by compatibility (i.e. any version in the list that is compatible with some version
1306   v in the list will preceed v)
1307   """
1308   def compare(a, b):
1309      if a == b:
1310         return 0
1311      if b in parentMap[a]:
1312         return -1
1313      if a in parentMap[b]:
1314         return 1
1315      return (a > b) - (a < b)
1316
1317   if PY3:
1318      return sorted([v for (v, n) in iteritems(serviceNsMap) if n == namespace],
1319                    key=cmp_to_key(compare))
1320   else:
1321      return sorted([v for (v, n) in iteritems(serviceNsMap) if n == namespace],
1322                    compare)
1323
1324
1325## Set a WSDL method with wsdl namespace and wsdl name
1326#  Internal to VmomiSupport
1327#  Note: Must be holding the _lazyLock
1328#
1329# @param ns XML namespace
1330# @param wsdlName wsdl name
1331# @param inputMM managed method object or info to load it (it points to
1332# list object that points to the type info which holds
1333# this managed method's information)
1334# @return returns added method or exising method if (ns, wsdlName)
1335# is already in the map. It throws a runtime error if
1336# trying to set two type info list's to the same (ns, wsdlName)
1337def _SetWsdlMethod(ns, wsdlName, inputMM):
1338   """
1339   Set a WSDL method with wsdl namespace and wsdl name
1340   Returns added method / existing method if (ns, wsdlName) already in the map
1341
1342   Note: Must be holding the _lazyLock
1343   """
1344   _wsdlMethodNSs.add(ns)
1345   curMM = _wsdlMethodMap.get( (ns, wsdlName) )
1346   # if inputMM is a list
1347   if isinstance(inputMM, list):
1348      if curMM is None:
1349         _wsdlMethodMap[(ns, wsdlName)] = inputMM
1350         return inputMM
1351      elif isinstance(curMM, list):
1352         raise RuntimeError(
1353                   "Duplicate wsdl method %s %s (new class %s vs existing %s)" % \
1354                   (ns, wsdlName, inputMM[0], curMM[0]))
1355      else:
1356         return curMM
1357   # if inputMM is a ManagedMethod
1358   else:
1359      if curMM is None or isinstance(curMM, list):
1360         _wsdlMethodMap[(ns, wsdlName)] = inputMM
1361         return inputMM
1362      else:
1363         return curMM
1364
1365## Get wsdl method from ns, wsdlName
1366# @param ns XML namespace
1367# @param wsdlName wsdl name
1368# @return managed method object or throws a KeyError
1369def GetWsdlMethod(ns, wsdlName):
1370   """ Get wsdl method from ns, wsdlName """
1371   with _lazyLock:
1372      method = _wsdlMethodMap[(ns, wsdlName)]
1373      if isinstance(method, ManagedMethod):
1374         # The type corresponding to the method is loaded,
1375         # just return the method object
1376         return method
1377      elif method:
1378         # The type is not loaded, the map contains the info
1379         # to load the type. Load the actual type and
1380         # return the method object
1381         LoadManagedType(*method)
1382         return _wsdlMethodMap[(ns, wsdlName)]
1383      else:
1384         raise KeyError("{0} {1}".format(ns, name))
1385
1386## Guess the method from wsdlname with no ns
1387#  WARNING! This should not be used in general, as there is no guarantee for
1388#  the correctness of the guessing method
1389# @param name wsdl name
1390# @return managed method object if found in any namespace else throws
1391# KeyError
1392def GuessWsdlMethod(name):
1393   with _lazyLock:
1394      # Some methods may exist in multiple namespaces, and returning
1395      # the wrong one will cause a deserialization error.
1396      # Since in python3 the order of entries in set is not deterministic,
1397      # we will try to get the method from vim25 namespace first.
1398      try:
1399         return GetWsdlMethod(XMLNS_VMODL_BASE, name)
1400      except KeyError:
1401         pass
1402
1403      for ns in _wsdlMethodNSs:
1404         try:
1405            return GetWsdlMethod(ns, name)
1406         except KeyError:
1407            pass
1408      raise KeyError(name)
1409
1410## Widen a type to one supported in a given version
1411def GetCompatibleType(type, version):
1412   # Type can be widened if it has the attribute "_version" (which implies it
1413   # is either a DataObject or ManagedObject)
1414   if hasattr(type, "_version"):
1415      while not IsChildVersion(version, type._version):
1416         type = type.__bases__[0]
1417   return type
1418
1419## Invert an injective mapping
1420def InverseMap(map):
1421   return dict([ (v, k) for (k, v) in iteritems(map) ])
1422
1423def GetVmodlNs(version):
1424   versionParts = version.split('.version.')
1425   assert len(versionParts) == 2, 'Unsupported version format: %s' % version
1426   return versionParts[0]
1427
1428types = Object()
1429nsMap = {}
1430versionIdMap = {}
1431versionMap = {}
1432serviceNsMap = { BASE_VERSION : XMLNS_VMODL_BASE.split(":")[-1] }
1433parentMap = {}
1434
1435class _MaturitySet:
1436   """
1437   Registry for versions from all namespaces defining a given maturity.
1438   The registration is automatic (relevant code is generated by emitters),
1439   while for the query one may use either the VMODL namespace id (e.g. 'vim'),
1440   or the wire namespace id (e.g. 'vim25').
1441   """
1442   def __init__(self):
1443      self._verNameMap = {}   # e.g. 'vim'   -> 'vim.version.version12'
1444      self._verNameMapW = {}  # e.g. 'vim25' -> 'vim.version.version12'
1445      self._wireIdMap = {}    # e.g. 'vim'   -> 'vim25/6.7'
1446      self._wireIdMapW = {}   # e.g. 'vim25' -> 'vim25/6.7'
1447
1448   def Add(self, version):
1449      """
1450      Register the version at corresponding maturity for a given VMODL
1451      namespace. The 'version' parameter is in the VMODL name format
1452      e.g. 'vim.version.version12'. This method is typically used by
1453      auto-generated code.
1454      """
1455      vmodlNs = GetVmodlNs(version)
1456
1457      # TODO fix the VSAN-related part of vcenter-all to enable the assert
1458      # assert not (vmodlNs in self._verNameMap), 'Re-definition: %s' % vmodlNs
1459
1460      wireId = GetVersionNamespace(version)
1461      wireNs = wireId.split('/')[0]
1462      self._verNameMap[vmodlNs] = version
1463      self._verNameMapW[wireNs] = version
1464      self._wireIdMap[vmodlNs] = wireId
1465      self._wireIdMapW[wireNs] = wireId
1466      return wireId, wireNs
1467
1468   def GetName(self, vmodlNs):
1469      """
1470      VMODL namespace to registered version name mapping, e.g.
1471      'vim' -> 'vim.version.version12'
1472      """
1473      return self._verNameMap[vmodlNs]
1474
1475   def GetNameW(self, wireNs):
1476      """
1477      Wire namespace to registered version name mapping, e.g.
1478      'vim25' -> 'vim.version.version12'
1479      """
1480      return self._verNameMapW[wireNs]
1481
1482   def GetWireId(self, vmodlNs):
1483      """
1484      VMODL namespace to registered version wire-id mapping, e.g.
1485      'vim' -> 'vim25/6.7'
1486      """
1487      return self._wireIdMap[vmodlNs]
1488
1489   def GetWireIdW(self, wireNs):
1490      """
1491      Wire namespace to registered version wire-id mapping, e.g.
1492      'vim25' -> 'vim25/6.7'
1493      """
1494      return self._wireIdMapW[wireNs]
1495
1496   def EnumerateVmodlNs(self):
1497      """
1498      Returns an iterable with registered VMODL namespace, e.g.
1499      ['vim', 'vpx', ... ]
1500      """
1501      return self._verNameMap.keys()
1502
1503   def EnumerateWireNs(self):
1504      """
1505      Returns an iterable with registered wire namespace, e.g.
1506      ['vim25', 'vpxd3', ... ]
1507      """
1508      return self._verNameMapW.keys()
1509
1510   def EnumerateVersions(self):
1511      """
1512      Returns an iterable with registered VMODL versions, e.g.
1513      ['vim.version.version12', 'vpx.version.version12', ... ]
1514      """
1515      return self._verNameMap.values()
1516
1517   def EnumerateWireIds(self):
1518      """
1519      Returns an iterable with registered versions wire-ids, e.g.
1520      e.g. ['vim25/6.7', 'vpxd3/6.7', ... ]
1521      """
1522      return self._wireIdMap.values()
1523
1524# Backward compatibility aliases
1525_MaturitySet.Get = _MaturitySet.GetName
1526_MaturitySet.GetNamespace = _MaturitySet.GetWireId
1527
1528
1529newestVersions = _MaturitySet()
1530ltsVersions = _MaturitySet()
1531dottedVersions = _MaturitySet()
1532oldestVersions = _MaturitySet()
1533
1534# Alias for backward compatibility.
1535publicVersions = ltsVersions
1536
1537from .Version import AddVersion, IsChildVersion
1538
1539if not isinstance(bool, type): # bool not a type in python <= 2.2
1540   bool = type("bool", (int,),
1541               {"__new__": lambda cls, val=0: int.__new__(cls, val and 1 or 0)})
1542byte  = type("byte", (int,), {})
1543short  = type("short", (int,), {})
1544double = type("double", (float,), {})
1545if PY3:
1546   long = type("long", (int,), {})
1547URI = type("URI", (str,), {})
1548if not PY3:
1549    # six defines binary_type in python2 as a string; this means the
1550    # JSON encoder sees checksum properties as strings and attempts
1551    # to perform utf-8 decoding on them because they contain high-bit
1552    # characters.
1553    binary = type("binary", (bytearray,), {})
1554else:
1555    binary = type("binary", (binary_type,), {})
1556PropertyPath = type("PropertyPath", (text_type,), {})
1557
1558# _wsdlTypeMapNSs store namespaces added to _wsdlTypeMap in _SetWsdlType
1559_wsdlTypeMapNSs = set()
1560_wsdlTypeMap = {
1561   # Note: xsd has no void type. This is a hack from before. Can be removed?
1562   (XMLNS_XSD, 'void') : NoneType,
1563   (XMLNS_XSD, 'anyType') : object,
1564   (XMLNS_XSD, 'boolean') : bool,
1565   (XMLNS_XSD, 'byte') : byte,
1566   (XMLNS_XSD, 'short') : short,
1567   (XMLNS_XSD, 'int') : int,
1568   (XMLNS_XSD, 'long') : long,
1569   (XMLNS_XSD, 'float') : float,
1570   (XMLNS_XSD, 'double') : double,
1571   (XMLNS_XSD, 'string') : str,
1572   (XMLNS_XSD, 'anyURI') : URI,
1573   (XMLNS_XSD, 'base64Binary') : binary,
1574   (XMLNS_XSD, 'dateTime') : datetime,
1575   (XMLNS_XSD, 'Link') : Link,
1576   (XMLNS_VMODL_BASE, 'TypeName') : type,
1577   (XMLNS_VMODL_BASE, 'MethodName') : ManagedMethod,
1578   (XMLNS_VMODL_BASE, 'PropertyPath') : PropertyPath
1579}
1580_wsdlNameMap = InverseMap(_wsdlTypeMap)
1581
1582for ((ns, name), typ) in list(_wsdlTypeMap.items()):
1583   if typ is not NoneType:
1584      setattr(types, typ.__name__, typ)
1585      _wsdlTypeMapNSs.add(ns)
1586      arrayType = CreateArrayType(typ)
1587      setattr(types, Capitalize(typ.__name__) + "Array", arrayType)
1588      arrayName = "ArrayOf" + Capitalize(name)
1589      arrayNS = XMLNS_VMODL_BASE
1590      _SetWsdlType(arrayNS, arrayName, arrayType)
1591      _wsdlNameMap[arrayType] = (arrayNS, arrayName)
1592del name, typ
1593
1594# unicode is mapped to wsdl name 'string' (Cannot put in wsdlTypeMap or name
1595# collision with non-unicode string)
1596_wsdlNameMap[text_type] = (XMLNS_XSD, 'string')
1597_wsdlNameMap[CreateArrayType(text_type)] = (XMLNS_VMODL_BASE, 'ArrayOfString')
1598
1599# _wsdlMethodNSs store namespaces added to _wsdlMethodMap in _SetWsdlMethod
1600_wsdlMethodNSs = set()
1601_wsdlMethodMap = {}
1602
1603# Registering the classes defined in VmomiSupport in the definition maps
1604CreateManagedType(ManagedObject.__name__, ManagedObject._wsdlName, None,
1605                  ManagedObject._version, [], [])
1606_AddType(ManagedObject)
1607setattr(types, ManagedObject.__name__, ManagedObject)
1608
1609CreateDataType(DataObject.__name__, DataObject._wsdlName, None,
1610               DataObject._version, [])
1611_AddType(DataObject)
1612setattr(types, DataObject.__name__, DataObject)
1613
1614## Vmodl types
1615vmodlTypes = {
1616   # Note: xsd has no void type. This is a hack from before. Can be removed?
1617   "void"   : GetWsdlType(XMLNS_XSD, 'void'),
1618   "anyType": GetWsdlType(XMLNS_XSD, 'anyType'),
1619   "string" : GetWsdlType(XMLNS_XSD, 'string'),
1620   "bool"   : GetWsdlType(XMLNS_XSD, 'boolean'),
1621   "boolean": GetWsdlType(XMLNS_XSD, 'boolean'),
1622   "byte"   : GetWsdlType(XMLNS_XSD, 'byte'),
1623   "short"  : GetWsdlType(XMLNS_XSD, 'short'),
1624   "int"    : GetWsdlType(XMLNS_XSD, 'int'),
1625   "long"   : GetWsdlType(XMLNS_XSD, 'long'),
1626   "float"  : GetWsdlType(XMLNS_XSD, 'float'),
1627   "double" : GetWsdlType(XMLNS_XSD, 'double'),
1628   "Link"   : GetWsdlType(XMLNS_XSD, 'Link'),
1629   "vmodl.URI"        : GetWsdlType(XMLNS_XSD, 'anyURI'),
1630   "vmodl.Binary"     : GetWsdlType(XMLNS_XSD, 'base64Binary'),
1631   "vmodl.DateTime"   : GetWsdlType(XMLNS_XSD, 'dateTime'),
1632   "vmodl.TypeName"   : GetWsdlType(XMLNS_VMODL_BASE, 'TypeName'),
1633   "vmodl.MethodName" : GetWsdlType(XMLNS_VMODL_BASE, 'MethodName'),
1634   "vmodl.DataObject" : GetWsdlType(XMLNS_VMODL_BASE, 'DataObject'),
1635   "vmodl.ManagedObject" : GetWsdlType(XMLNS_VMODL_BASE, 'ManagedObject'),
1636   "vmodl.PropertyPath"  : GetWsdlType(XMLNS_VMODL_BASE, 'PropertyPath'),
1637}
1638vmodlNames = {}
1639
1640## Add array type into special names
1641for name, typ in vmodlTypes.copy().items():
1642   if typ is not NoneType:
1643      try:
1644         arrayType = typ.Array
1645      except AttributeError:
1646         wsdlName = GetWsdlName(typ)
1647         arrayNS = XMLNS_VMODL_BASE
1648         arrayType = GetWsdlType(arrayNS, "ArrayOf" + Capitalize(wsdlName))
1649      arrayName = name + "[]"
1650      vmodlTypes[arrayName] = arrayType
1651
1652   # Set type to vmodl name map
1653   vmodlNames[typ] = name
1654   vmodlNames[arrayType] = arrayName
1655del name, typ
1656
1657
1658## Get type from vmodl name
1659#
1660# @param name vmodl name
1661# @return vmodl type
1662def GetVmodlType(name):
1663   """ Get type from vmodl name """
1664
1665   # If the input is already a type, just return
1666   if isinstance(name, type):
1667      return name
1668
1669   # Try to get type from vmodl type names table
1670   typ = vmodlTypes.get(name)
1671   if typ:
1672      return typ
1673
1674   # Else get the type from the _wsdlTypeMap
1675   isArray = name.endswith("[]")
1676   if isArray:
1677      name = name[:-2]
1678   ns, wsdlName = _GetWsdlInfo(name)
1679   try:
1680      typ = GetWsdlType(ns, wsdlName)
1681   except KeyError:
1682      raise KeyError(name)
1683   if typ:
1684      return isArray and typ.Array or typ
1685   else:
1686      raise KeyError(name)
1687
1688## Get VMODL type name from type
1689#
1690# @param typ vmodl type
1691# @return vmodl name
1692def GetVmodlName(typ):
1693   """ Get vmodl type name from type """
1694   try:
1695      return vmodlNames[typ]
1696   except KeyError:
1697      return typ.__name__
1698
1699## Get Wsdl type name from Python type name
1700#
1701# @param pythonTypeName Python type name
1702# @return wsdl type name
1703def GetWsdlTypeName(pythonTypeName):
1704   try:
1705      typ = GetVmodlType(pythonTypeName)
1706   except KeyError:
1707      raise NameError('No type found with name ' + pythonTypeName)
1708   return GetWsdlName(typ)
1709
1710## Get Wsdl method name from Python method name
1711#
1712# @param pythonTypeName Python type name
1713# @param pythonMethodName Python method name
1714# @return wsdl method name
1715def GetWsdlMethodName(pythonTypeName, pythonMethodName):
1716   try:
1717      typ = GetVmodlType(pythonTypeName)
1718      _, _, _, _, _, methods = _wsdlDefMap[GetQualifiedWsdlName(typ)]
1719   except KeyError:
1720      raise NameError('No type found with name ' + pythonTypeName)
1721   uncapPythonMethodName = Uncapitalize(pythonMethodName)
1722   for method in methods:
1723      mVmodl, mWsdl, _, _, _, _, _ = method
1724      if mVmodl == uncapPythonMethodName or mVmodl == pythonMethodName:
1725         return mWsdl
1726   raise NameError('No method found with name ' + pythonMethodName)
1727
1728## Get Python type name from Wsdl type name
1729#
1730# @param ns wsdl namespace
1731# @param wsdlTypeName wsdl type name
1732# @return python type name
1733def GetPythonTypeName(wsdlTypeName, ns):
1734   try:
1735      typ = GetWsdlType(ns, wsdlTypeName)
1736   except KeyError:
1737      raise NameError('No type found with namespace %s and name %s' % (ns, wsdlTypeName))
1738   return GetVmodlName(typ)
1739
1740## Get Python method name from Wsdl method name
1741#
1742# @param ns wsdl namespace
1743# @param wsdlTypeName wsdl type name
1744# @param wsdlMethodName wsdl method name
1745# @return python method name
1746def GetPythonMethodName(wsdlTypeName, ns, wsdlMethodName):
1747   try:
1748      _, _, _, _, _, methods = _wsdlDefMap[(ns, wsdlTypeName)]
1749   except KeyError:
1750      raise NameError('No type found with namespace %s and name %s' % (ns, wsdlTypeName))
1751   for method in methods:
1752      mVmodl, mWsdl, _, _, _, _, _ = method
1753      if mWsdl == wsdlMethodName:
1754         return Capitalize(mVmodl)
1755   raise NameError('No method found with name ' + wsdlMethodName)
1756
1757## String only dictionary: same as dict, except it only accept string as value
1758#
1759class StringDict(dict):
1760   """
1761   String only dictionary: same as dict, except it only accept string as value
1762
1763   dict in python is kind of strange. U cannot just override __setitem__, as
1764   __init__, update, and setdefault all bypass __setitem__. When override,
1765   we have to override all three together
1766   """
1767   def __init__(self, *args, **kwargs):
1768      dict.__init__(self)
1769      self.update(*args, **kwargs)
1770
1771   # Same as dict setdefault, except this will call through our __setitem__
1772   def update(self, *args, **kwargs):
1773      for k, v in iteritems(dict(*args, **kwargs)):
1774         self[k] = v
1775
1776   # Same as dict setdefault, except this will call through our __setitem__
1777   def setdefault(self, key, val=None):
1778      if key in self:
1779         return self[key]
1780      else:
1781         self[key] = val
1782         return val
1783
1784   def __setitem__(self, key, val):
1785      """x.__setitem__(i, y) <==> x[i]=y, where y must be a string"""
1786      if not isinstance(val, string_types):
1787         raise TypeError("key %s has non-string value %s of %s" %
1788                                                         (key, val, type(val)))
1789      return dict.__setitem__(self, key, val)
1790
1791## Retrieves the actual vmodl name from type dictionaries
1792#
1793#  Note: Must be holding the _lazyLock
1794# @param name upcapitalized vmodl name
1795# @return vmodl name
1796def _GetActualName(name):
1797   """ Note: Must be holding the _lazyLock """
1798   if _allowCapitalizedNames:
1799      name = UncapitalizeVmodlName(name)
1800   for defMap in _dataDefMap, _managedDefMap, _enumDefMap:
1801      dic = defMap.get(name)
1802      if dic:
1803         return dic[0]
1804   return None
1805
1806## Retrieves the actual wsdl name from type dictionaries
1807#
1808# @param name upcapitalized vmodl name
1809# @return (wsdl namespace, wsdl name)
1810def _GetWsdlInfo(name):
1811   if _allowCapitalizedNames:
1812      name = UncapitalizeVmodlName(name)
1813
1814   with _lazyLock:
1815      # For data and managed objects, emitter puts version in field #3 and in
1816      # enum objects, it is in field #2. So, have to handle them differently
1817      for defMap in _dataDefMap, _managedDefMap:
1818         dic = defMap.get(name)
1819         if dic:
1820            return GetWsdlNamespace(dic[3]), dic[1]
1821
1822      dic = _enumDefMap.get(name)
1823      if dic:
1824         return GetWsdlNamespace(dic[2]), dic[1]
1825      return None, None
1826
1827## Checks if the definition exists for a vmodl name
1828#
1829# @param name vmodl name
1830# @return True if name exists, False otherwise
1831def TypeDefExists(name):
1832   # Check if is one of the primitive types
1833   typ = vmodlTypes.get(name)
1834   if typ:
1835      return True
1836
1837   # Check if it's type definition is loaded in the dictionaries
1838   if name.endswith("[]"):
1839      name = name[:-2]
1840
1841   with _lazyLock:
1842      actualName = _GetActualName(name)
1843   return actualName is not None
1844
1845# Thread local for req context
1846_threadLocalContext = threading.local()
1847
1848# Get the RequestContext for the current thread
1849#
1850def GetRequestContext():
1851   """ Get the RequestContext for the current thread """
1852   global _threadLocalContext
1853   return _threadLocalContext.__dict__.setdefault('reqCtx', StringDict())
1854
1855# Get the Http context for the current thread
1856#
1857def GetHttpContext():
1858   """ Get the Http context for the current thread """
1859   global _threadLocalContext
1860   return _threadLocalContext.__dict__.setdefault('httpCtx', dict())
1861
1862## Class that resolves links
1863class LinkResolver:
1864   ## Constructor
1865   #
1866   # @param self self
1867   # @param scope DataObject to be used against for resolving links
1868   def __init__(self, scope):
1869      self.linkables = {}
1870      self._VisitDataObject(scope)
1871
1872   ## Visit a DataObject and add it to linkable if it is one. Also
1873   #  visit its properties that are DataObjects
1874   #
1875   # @param self self
1876   # @param obj DataObject to be visited
1877   def _VisitDataObject(self, obj):
1878      if isinstance(obj, DataObject):
1879         for prop in obj._GetPropertyList():
1880            if issubclass(prop.type, list):
1881               for dataObj in getattr(obj, prop.name):
1882                  if (prop.flags & F_LINKABLE):
1883                     self._AddLinkable(dataObj)
1884                  self._VisitDataObject(dataObj)
1885            else:
1886               dataObj = getattr(obj, prop.name)
1887               if (prop.flags & F_LINKABLE):
1888                  self._AddLinkable(dataObj)
1889               self._VisitDataObject(dataObj)
1890      elif isinstance(obj, list):
1891         for dataObj in obj:
1892            self._VisitDataObject(dataObj)
1893
1894   ## Adds a DataObject to linkable dictionary using its key
1895   #
1896   # @param self self
1897   # @param obj DataObject to be added to linkable
1898   def _AddLinkable(self, obj):
1899      key = getattr(obj, "key")
1900      if key and key != '':
1901         if key in self.linkables:
1902            #duplicate key present
1903            raise AttributeError(key)
1904         else:
1905            self.linkables[key] = obj
1906      else:
1907         #empty key
1908         raise AttributeError(key)
1909
1910   ## Resolves a key by looking up linkable dictionary
1911   #
1912   # @param self self
1913   # @param key Key to be resolved
1914   def ResolveLink(self, key):
1915      val = self.linkables[key]
1916      return val
1917
1918   ## Resolves a list of keys by resolving each key
1919   #
1920   # @param self self
1921   # @param keys keys to be resolved
1922   def ResolveLinks(self, keys):
1923      val = [self.linkables[k] for k in keys]
1924      return val
1925
1926## Resolves a link key using the object provided as its scope by creating a
1927#  link resolver object
1928#
1929# @param key Key to be resolved
1930# @param obj DataObject to be used against for resolving links
1931def ResolveLink(key, obj):
1932   if obj is None:
1933      return None
1934   linkResolver = LinkResolver(obj)
1935   return linkResolver.ResolveLink(key)
1936
1937## Resolves a list of link keys using the object provided as its scope by creating a
1938#  link resolver object
1939#
1940# @param keys keys to be resolved
1941# @param obj DataObject to be used against for resolving links
1942def ResolveLinks(keys, obj):
1943   if obj is None:
1944      return None
1945   linkResolver = LinkResolver(obj)
1946   return linkResolver.ResolveLinks(keys)
1947
1948
1949# Dictionary of type { 'branch' : { 'namespace' : count } }
1950# used by Vmodl Vcdb B2B code.
1951
1952_breakingChanges = {}
1953
1954
1955def AddBreakingChangesInfo(branchName, vmodlNamespace, count):
1956   _breakingChanges.setdefault(branchName, {})[vmodlNamespace] = count
1957
1958
1959def GetBreakingChanges():
1960   return _breakingChanges
1961