1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

vmware_inventory: fix the --host and add 'properties' feature to ini (#18072)

* vmware_inventory: fix the --host option
* Fix skip_key evaluation
* Short circuit deep dives in datastores and resourcegroups
* Put timestamps in the debug output and add a few more
* Implement a user defined proplist to increase performance
* Make all props into dicts
* Update ini with example
* Fix tests
This commit is contained in:
jctanner 2016-10-18 16:21:37 -04:00 committed by GitHub
parent 5037dc4e69
commit 05aed6e52e
2 changed files with 182 additions and 22 deletions

View file

@ -69,3 +69,26 @@ password=vmware
# because those values will become the literal group name. The patterns can be # because those values will become the literal group name. The patterns can be
# comma delimited to create as many groups as necessary # comma delimited to create as many groups as necessary
#groupby_patterns={{ guest.guestid }},{{ 'templates' if config.template else 'guests'}} #groupby_patterns={{ guest.guestid }},{{ 'templates' if config.template else 'guests'}}
# The script attempts to recurse into virtualmachine objects and serialize
# all available data. The serialization is comprehensive but slow. If the
# vcenter environment is large and the desired properties are known, create
# a 'properties' section in this config and make an arbitrary list of
# key=value settings where the value is a path to a specific property. If
# If this feature is enabled, be sure to fetch every property that is used
# in the jinja expressions defined above. For performance tuning, reduce
# the number of properties to the smallest amount possible and limit the
# use of properties that are not direct attributes of vim.VirtualMachine
#[properties]
#prop01=name
#prop02=config.cpuHotAddEnabled
#prop03=config.cpuHotRemoveEnabled
#prop04=config.instanceUuid
#prop05=config.hardware.numCPU
#prop06=config.template
#prop07=config.name
#prop08=guest.hostName
#prop09=guest.ipAddress
#prop10=guest.guestId
#prop11=guest.guestState
#prop12=runtime.maxMemoryUsage

View file

@ -58,11 +58,14 @@ try:
except ImportError: except ImportError:
pass pass
class VMwareMissingHostException(Exception):
pass
class VMWareInventory(object): class VMWareInventory(object):
__name__ = 'VMWareInventory' __name__ = 'VMWareInventory'
guest_props = False
instances = [] instances = []
debug = False debug = False
load_dumpfile = None load_dumpfile = None
@ -80,14 +83,30 @@ class VMWareInventory(object):
host_filters = [] host_filters = []
groupby_patterns = [] groupby_patterns = []
bad_types = ['Array', 'disabledMethod', 'declaredAlarmState']
if (sys.version_info > (3, 0)): if (sys.version_info > (3, 0)):
safe_types = [int, bool, str, float, None] safe_types = [int, bool, str, float, None]
else: else:
safe_types = [int, long, bool, str, float, None] safe_types = [int, long, bool, str, float, None]
iter_types = [dict, list] iter_types = [dict, list]
skip_keys = ['dynamicproperty', 'dynamictype', 'managedby', 'childtype']
bad_types = ['Array', 'disabledMethod', 'declaredAlarmState']
skip_keys = ['declaredalarmstate',
'disabledmethod',
'dynamicproperty',
'dynamictype',
'environmentbrowser',
'managedby',
'parent',
'childtype']
# translation table for attributes to fetch for known vim types
if not HAS_PYVMOMI:
vimTable = {}
else:
vimTable = {
vim.Datastore: ['_moId', 'name'],
vim.ResourcePool: ['_moId', 'name'],
}
def _empty_inventory(self): def _empty_inventory(self):
return {"_meta" : {"hostvars" : {}}} return {"_meta" : {"hostvars" : {}}}
@ -108,7 +127,7 @@ class VMWareInventory(object):
if self.args.refresh_cache or not cache_valid: if self.args.refresh_cache or not cache_valid:
self.do_api_calls_update_cache() self.do_api_calls_update_cache()
else: else:
self.debugl('# loading inventory from cache') self.debugl('loading inventory from cache')
self.inventory = self.get_inventory_from_cache() self.inventory = self.get_inventory_from_cache()
def debugl(self, text): def debugl(self, text):
@ -117,10 +136,11 @@ class VMWareInventory(object):
text = str(text) text = str(text)
except UnicodeEncodeError: except UnicodeEncodeError:
text = text.encode('ascii','ignore') text = text.encode('ascii','ignore')
print(text) print('%s %s' % (datetime.datetime.now(), text))
def show(self): def show(self):
# Data to print # Data to print
self.debugl('dumping results')
data_to_print = None data_to_print = None
if self.args.host: if self.args.host:
data_to_print = self.get_host_info(self.args.host) data_to_print = self.get_host_info(self.args.host)
@ -222,30 +242,46 @@ class VMWareInventory(object):
# set the cache filename and max age # set the cache filename and max age
cache_name = config.get('vmware', 'cache_name') cache_name = config.get('vmware', 'cache_name')
self.cache_path_cache = self.cache_dir + "/%s.cache" % cache_name self.cache_path_cache = self.cache_dir + "/%s.cache" % cache_name
self.debugl('cache path is %s' % self.cache_path_cache)
self.cache_max_age = int(config.getint('vmware', 'cache_max_age')) self.cache_max_age = int(config.getint('vmware', 'cache_max_age'))
# mark the connection info # mark the connection info
self.server = os.environ.get('VMWARE_SERVER', config.get('vmware', 'server')) self.server = os.environ.get('VMWARE_SERVER', config.get('vmware', 'server'))
self.debugl('server is %s' % self.server)
self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port'))) self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port')))
self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username')) self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username'))
self.debugl('username is %s' % self.username)
self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password')) self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password'))
self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs')) self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs'))
if self.validate_certs in ['no', 'false', 'False', False]: if self.validate_certs in ['no', 'false', 'False', False]:
self.validate_certs = False self.validate_certs = False
else: else:
self.validate_certs = True self.validate_certs = True
self.debugl('cert validation is %s' % self.validate_certs)
# behavior control # behavior control
self.maxlevel = int(config.get('vmware', 'max_object_level')) self.maxlevel = int(config.get('vmware', 'max_object_level'))
self.debugl('max object level is %s' % self.maxlevel)
self.lowerkeys = config.get('vmware', 'lower_var_keys') self.lowerkeys = config.get('vmware', 'lower_var_keys')
if type(self.lowerkeys) != bool: if type(self.lowerkeys) != bool:
if str(self.lowerkeys).lower() in ['yes', 'true', '1']: if str(self.lowerkeys).lower() in ['yes', 'true', '1']:
self.lowerkeys = True self.lowerkeys = True
else: else:
self.lowerkeys = False self.lowerkeys = False
self.debugl('lower keys is %s' % self.lowerkeys)
self.host_filters = list(config.get('vmware', 'host_filters').split(',')) self.host_filters = list(config.get('vmware', 'host_filters').split(','))
self.debugl('host filters are %s' % self.host_filters)
self.groupby_patterns = list(config.get('vmware', 'groupby_patterns').split(',')) self.groupby_patterns = list(config.get('vmware', 'groupby_patterns').split(','))
self.debugl('groupby patterns are %s' % self.groupby_patterns)
# Special feature to disable the brute force serialization of the
# virtulmachine objects. The key name for these properties does not
# matter because the values are just items for a larger list.
if config.has_section('properties'):
self.guest_props = []
for prop in config.items('properties'):
self.guest_props.append(prop[1])
# save the config # save the config
self.config = config self.config = config
@ -296,7 +332,7 @@ class VMWareInventory(object):
instances = [] instances = []
si = SmartConnect(**inkwargs) si = SmartConnect(**inkwargs)
self.debugl('# retrieving instances') self.debugl('retrieving all instances')
if not si: if not si:
print("Could not connect to the specified host using specified " print("Could not connect to the specified host using specified "
"username and password") "username and password")
@ -305,6 +341,7 @@ class VMWareInventory(object):
content = si.RetrieveContent() content = si.RetrieveContent()
# Create a search container for virtualmachines # Create a search container for virtualmachines
self.debugl('creating containerview for virtualmachines')
container = content.rootFolder container = content.rootFolder
viewType = [vim.VirtualMachine] viewType = [vim.VirtualMachine]
recursive = True recursive = True
@ -316,12 +353,19 @@ class VMWareInventory(object):
if len(instances) >= (self.args.max_instances): if len(instances) >= (self.args.max_instances):
break break
instances.append(child) instances.append(child)
self.debugl("# total instances retrieved %s" % len(instances)) self.debugl("%s total instances in container view" % len(instances))
if self.args.host:
instances = [x for x in instances if x.name == self.args.host]
instance_tuples = [] instance_tuples = []
for instance in sorted(instances): for instance in sorted(instances):
ifacts = self.facts_from_vobj(instance) if self.guest_props != False:
ifacts = self.facts_from_proplist(instance)
else:
ifacts = self.facts_from_vobj(instance)
instance_tuples.append((instance, ifacts)) instance_tuples.append((instance, ifacts))
self.debugl('facts collected for all instances')
return instance_tuples return instance_tuples
@ -329,6 +373,7 @@ class VMWareInventory(object):
''' Convert a list of vm objects into a json compliant inventory ''' ''' Convert a list of vm objects into a json compliant inventory '''
self.debugl('re-indexing instances based on ini settings')
inventory = self._empty_inventory() inventory = self._empty_inventory()
inventory['all'] = {} inventory['all'] = {}
inventory['all']['hosts'] = [] inventory['all']['hosts'] = []
@ -380,14 +425,14 @@ class VMWareInventory(object):
inventory['all']['hosts'].remove(k) inventory['all']['hosts'].remove(k)
inventory['_meta']['hostvars'].pop(k, None) inventory['_meta']['hostvars'].pop(k, None)
self.debugl('# pre-filtered hosts:') self.debugl('pre-filtered hosts:')
for i in inventory['all']['hosts']: for i in inventory['all']['hosts']:
self.debugl('# * %s' % i) self.debugl(' * %s' % i)
# Apply host filters # Apply host filters
for hf in self.host_filters: for hf in self.host_filters:
if not hf: if not hf:
continue continue
self.debugl('# filter: %s' % hf) self.debugl('filter: %s' % hf)
filter_map = self.create_template_mapping(inventory, hf, dtype='boolean') filter_map = self.create_template_mapping(inventory, hf, dtype='boolean')
for k,v in filter_map.iteritems(): for k,v in filter_map.iteritems():
if not v: if not v:
@ -395,9 +440,9 @@ class VMWareInventory(object):
inventory['all']['hosts'].remove(k) inventory['all']['hosts'].remove(k)
inventory['_meta']['hostvars'].pop(k, None) inventory['_meta']['hostvars'].pop(k, None)
self.debugl('# post-filter hosts:') self.debugl('post-filter hosts:')
for i in inventory['all']['hosts']: for i in inventory['all']['hosts']:
self.debugl('# * %s' % i) self.debugl(' * %s' % i)
# Create groups # Create groups
for gbp in self.groupby_patterns: for gbp in self.groupby_patterns:
@ -439,6 +484,55 @@ class VMWareInventory(object):
mapping[k] = newkey mapping[k] = newkey
return mapping return mapping
def facts_from_proplist(self, vm):
'''Get specific properties instead of serializing everything'''
rdata = {}
for prop in self.guest_props:
self.debugl('getting %s property for %s' % (prop, vm.name))
key = prop
if self.lowerkeys:
key = key.lower()
if not '.' in prop:
# props without periods are direct attributes of the parent
rdata[key] = getattr(vm, prop)
else:
# props with periods are subkeys of parent attributes
parts = prop.split('.')
total = len(parts) - 1
# pointer to the current object
val = None
# pointer to the current result key
lastref = rdata
for idx,x in enumerate(parts):
# if the val wasn't set yet, get it from the parent
if not val:
val = getattr(vm, x)
else:
# in a subkey, get the subprop from the previous attrib
try:
val = getattr(val, x)
except AttributeError as e:
self.debugl(e)
# lowercase keys if requested
if self.lowerkeys:
x = x.lower()
# change the pointer or set the final value
if idx != total:
if x not in lastref:
lastref[x] = {}
lastref = lastref[x]
else:
lastref[x] = val
return rdata
def facts_from_vobj(self, vobj, level=0): def facts_from_vobj(self, vobj, level=0):
@ -454,7 +548,7 @@ class VMWareInventory(object):
if level == 0: if level == 0:
try: try:
self.debugl("# get facts: %s" % vobj.name) self.debugl("get facts for %s" % vobj.name)
except Exception as e: except Exception as e:
self.debugl(e) self.debugl(e)
@ -463,6 +557,7 @@ class VMWareInventory(object):
methods = dir(vobj) methods = dir(vobj)
methods = [str(x) for x in methods if not x.startswith('_')] methods = [str(x) for x in methods if not x.startswith('_')]
methods = [x for x in methods if not x in self.bad_types] methods = [x for x in methods if not x in self.bad_types]
methods = [x for x in methods if not x.lower() in self.skip_keys]
methods = sorted(methods) methods = sorted(methods)
for method in methods: for method in methods:
@ -471,22 +566,35 @@ class VMWareInventory(object):
methodToCall = getattr(vobj, method) methodToCall = getattr(vobj, method)
except Exception as e: except Exception as e:
continue continue
# Skip callable methods # Skip callable methods
if callable(methodToCall): if callable(methodToCall):
continue continue
if self.lowerkeys: if self.lowerkeys:
method = method.lower() method = method.lower()
rdata[method] = self._process_object_types(methodToCall)
rdata[method] = self._process_object_types(
methodToCall,
thisvm=vobj,
inkey=method
)
return rdata return rdata
def _process_object_types(self, vobj, level=0): def _process_object_types(self, vobj, thisvm=None, inkey=None, level=0):
''' Serialize an object ''' ''' Serialize an object '''
rdata = {} rdata = {}
if vobj is None: if vobj is None:
rdata = None rdata = None
elif type(vobj) in self.vimTable:
rdata = {}
for key in self.vimTable[type(vobj)]:
rdata[key] = getattr(vobj, key)
elif issubclass(type(vobj), str) or isinstance(vobj, str): elif issubclass(type(vobj), str) or isinstance(vobj, str):
if vobj.isalnum(): if vobj.isalnum():
rdata = vobj rdata = vobj
@ -506,18 +614,29 @@ class VMWareInventory(object):
vobj = sorted(vobj) vobj = sorted(vobj)
except Exception as e: except Exception as e:
pass pass
for vi in vobj:
for idv, vii in enumerate(vobj):
if (level+1 <= self.maxlevel): if (level+1 <= self.maxlevel):
#vid = self.facts_from_vobj(vi, level=(level+1))
vid = self._process_object_types(vi, level=(level+1)) vid = self._process_object_types(
vii,
thisvm=thisvm,
inkey=inkey+'['+str(idv)+']',
level=(level+1)
)
if vid: if vid:
rdata.append(vid) rdata.append(vid)
elif issubclass(type(vobj), dict): elif issubclass(type(vobj), dict):
pass pass
elif issubclass(type(vobj), object): elif issubclass(type(vobj), object):
methods = dir(vobj) methods = dir(vobj)
methods = [str(x) for x in methods if not x.startswith('_')] methods = [str(x) for x in methods if not x.startswith('_')]
methods = [x for x in methods if not x in self.bad_types] methods = [x for x in methods if not x in self.bad_types]
methods = [x for x in methods if not x.lower() in self.skip_keys]
methods = sorted(methods) methods = sorted(methods)
for method in methods: for method in methods:
@ -531,19 +650,37 @@ class VMWareInventory(object):
if self.lowerkeys: if self.lowerkeys:
method = method.lower() method = method.lower()
if (level+1 <= self.maxlevel): if (level+1 <= self.maxlevel):
rdata[method] = self._process_object_types(methodToCall, level=(level+1)) rdata[method] = self._process_object_types(
methodToCall,
thisvm=thisvm,
inkey=inkey+'.'+method,
level=(level+1)
)
else: else:
pass pass
if not rdata:
rdata = None
return rdata return rdata
def get_host_info(self, host): def get_host_info(self, host):
''' Return hostvars for a single host ''' ''' Return hostvars for a single host '''
return self.inventory['_meta']['hostvars'][host] if host in self.inventory['_meta']['hostvars']:
return self.inventory['_meta']['hostvars'][host]
elif self.args.host and self.inventory['_meta']['hostvars']:
# check if the machine has the name requested
keys = self.inventory['_meta']['hostvars'].keys()
match = None
for k,v in self.inventory['_meta']['hostvars'].iteritems():
if self.inventory['_meta']['hostvars'][k]['name'] == self.args.host:
match = k
break
if match:
return self.inventory['_meta']['hostvars'][match]
else:
raise VMwareMissingHostException('%s not found' % host)
else:
raise VMwareMissingHostException('%s not found' % host)
if __name__ == "__main__": if __name__ == "__main__":