You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

437 lines
16 KiB

# Copyright (C) 2005-2023 Splunk Inc. All Rights Reserved.
#Core Python imports
import json
from hydra import six
#TA-vmware imports
from vim25.mo import ManagedObjectReference
from vim25 import logger
from vim25 import utils
from vim25.connection import Connection
import vim25.suds_resolver
from suds import WebFault
# Start Class Object definitions.
class _targetConfig(object):
targetEntities = [
"VirtualMachine",
"HostSystem",
"Folder",
"Datacenter",
"ComputeResource",
"ClusterComputeResource",
"Datastore",
"ResourcePool"
]
Hierarchy = {
"VirtualMachine":["name", "parent", "runtime.host", "resourcePool"],
"HostSystem":["name", "parent"],
"Folder":["name", "parent"],
"Datacenter":["name", "parent"],
"ComputeResource":["name", "parent"],
"ClusterComputeResource":["name", "parent"],
"ResourcePool":["name", "parent"],
"Datastore":["name", "parent"]
}
VirtualMachine = {
"VirtualMachine":[
"name",
"parent",
"capability",
"config",
"datastore",
"environmentBrowser",
"guest",
"guestHeartbeatStatus",
"layoutEx",
"network",
"parentVApp",
"resourceConfig",
"resourcePool",
"rootSnapshot",
"snapshot",
"storage",
"summary"
]
}
@property
def HostSystem(self):
host_props_base = [
"name",
"parent",
"capability",
"datastore",
"config.hyperThread",
"network",
"summary",
"vm"
]
if not Connection.vc_version or Connection.vc_version[0] == '4':
props = host_props_base
else:
props = host_props_base + [ "licensableResource" ]
return { "HostSystem": props }
# Missing HostSystem.harware... but it seems... useless... or should say, 100 pages of junk for 2 things worth value
HostSystemSummary = {
"HostSystem":[
"summary"
]
}
HostSystemSystemResources = {
"HostSystem":[
"systemResources"
]
}
PerfInventory = {
"VirtualMachine":[
"name",
"runtime.host",
"summary.runtime.powerState"
]
}
HostList = {
"HostSystem":[
"name"
]
}
HostListConnectedPowered = {
"HostSystem":[
"name",
"summary.runtime.connectionState",
"summary.runtime.powerState"
]
}
PerfResourcePoolList = {
"ResourcePool":[
"name"
]
}
ResourcePool = {
"ResourcePool":[
"name",
"parent",
"config",
"owner",
"resourcePool",
"runtime",
"summary"
]
}
PerfClusterComputeResourceList = {
"ClusterComputeResource":[
"name"
]
}
ClusterComputeResource = {
"ClusterComputeResource":[
"name",
"parent",
"actionHistory",
"configurationEx",
"drsFault",
"recommendation",
"migrationHistory"
]
}
Datastore = {
"Datastore":[
"name",
"parent",
"capability",
"host",
"info",
"iormConfiguration",
"summary",
"vm"
]
}
targetConfig = _targetConfig()
class CurrentViewManager(Connection):
cViewRefList = None
if cViewRefList and len(cViewRefList) > 0:
cViewRef = cViewRefList[-1]
else:
cViewRef = None
@classmethod
def updateViewMgr(cls):
''' After creation / deletion of a view, this method updates cViewRefList with a list
of all currently active views for the user session
'''
cls.viewMgrRef = Connection.svcInstance.getViewManager()
@classmethod
def updateViewList(cls):
''' After creation / deletion of a view, this method updates cViewRefList with a list
of all currently active views for the user session, selects the last created view as
the target view for filter creation.
'''
cls.cViewRefList = cls.viewMgrRef.getViewList()
if len(cls.cViewRefList) > 0:
cls.cViewRef = cls.cViewRefList[-1]
else:
cls.cViewRef = None
@classmethod
def buildView(cls, entityList):
''' Creates a viewManger object that will traverse from the root folder and select
a list of objects specified in the entityList. This view can then be used as an object
to pass to propertyCollectors to traverse a large set of managed objects. Example:
If you wanted to collect all virtual machines in the root folder, you'd simply call
buildview(["VirtualMachine"])'''
cls.viewMgrRef.createContainerView(cls.rootFolder, entityList, True)
cls.updateViewList()
@classmethod
def destroyView(cls, viewListIndex):
''' Used to remove a view that's been created. Specify the index list of the target
view to remove. Views are accessed on PCollector.cViewRefList
'''
try:
cls.cViewRefList[viewListIndex].destroyView()
cls.updateViewList()
except Exception as e:
logger.error("Missing View, or view list index out of range: %s", str(e))
logger.exception(e)
class PropCollector(CurrentViewManager):
def __init__(self, mor=None):
try:
CurrentViewManager.updateViewMgr()
CurrentViewManager.updateViewList()
if mor:
if type(mor) != str:
raise
else:
tempMOR = ManagedObjectReference(value=mor, _type="PropertyCollector")
# tempMOR = Connection.vim25client.new('_this', value=mor, _type="PropertyCollector")
self.targetCollector = Connection.vim25client.createExactManagedObject(mor=tempMOR)
else:
self.targetCollector = CurrentViewManager.propColl.createPropertyCollector()
if not CurrentViewManager.cViewRef:
CurrentViewManager.buildView(targetConfig.targetEntities)
self.oSpec = None
self.tSpec = CurrentViewManager.tSpec
self.filterList = self.targetCollector.getFilter('filter')
self.fSpecList = []
self.pSpecList = []
except Exception as e:
logger.error("Error, when specifying an mor, please pass just the mor value, not an object: %s", str(e))
logger.exception(e)
def updateFilterList(self):
''' After creation / deletion of a filter, this method updates filterList with a list
of all currently active filters for the user session
'''
self.filterList = self.targetCollector.getFilter('filter')
def buildFilterSpecList(self, targetDict):
''' Takes the current connection object and a dictionary of target items to pull
during property collection. Example, if I wanted the names of all virtual machines,
I'd pass in a dict of {"VirtualMachine":"name"}. If I wanted both the name and the
parent of a virtual machine, my dict would look like:
{"VirtualMachine":["name","parent"]}. This command requires a view reference. Please
use 'buildView' prior to running this method.
'''
self.pSpecList = []
self.oSpec = Connection.vim25client.new('ObjectSpec', obj=CurrentViewManager.cViewRef.getMOR(), skip=True)
self.oSpec.selectSet.append(self.tSpec)
for targetType, targetPaths in six.iteritems(targetDict):
if type(targetPaths) == str:
self.pSpecList.append(Connection.vim25client.new('PropertySpec', type=targetType, pathSet=targetPaths))
elif type(targetPaths) == list:
for targetPath in targetPaths:
self.pSpecList.append(Connection.vim25client.new('PropertySpec', type=targetType, pathSet=targetPath))
self.fSpecList.append(Connection.vim25client.new('PropertyFilterSpec', objectSet=self.oSpec, propSet=self.pSpecList))
def emptyFilterSpecList(self):
''' Empties the fSpecList attribute on the class object. Useful for creating multiple
filters to use with checkForUpdate method. For instance, if you wanted to create a
hierarchy that lists the name and parent of vm's, hosts, clusters, folders and datacenters,
you'd create a spec list for {"VirtualMachine":["name","parent"]}, then run the buildFilter
method, then emptyFilterSpecList, then buildFilterSpecList with {"HostSystem":["name","parent"]}
, empty, rinse and repeat until finished. At the end you will have 5 items in your
FilterList that will all be called and record changes with one checkForUpdates method.
'''
self.fSpecList = []
def buildFilter(self, filtrList, partUpdates):
''' This method will create a filter managed object for propertyCollectors.
this filter will remain persistent until destroyFilter() is called or the session
is closed. This method is most often called after buildFilterList.
'''
self.targetCollector.createFilter(spec=filtrList, partialUpdates=partUpdates)
self.updateFilterList()
def destroyFilter(self, filterListIndex):
''' Used to remove a filter that's been created. Specify the index list of the target
filter to remove. Filters are accessed on PCollector.filterList
'''
try:
self.filterList[filterListIndex].destroyPropertyFilter()
self.updateFilterList()
except Exception as e:
logger.error("Missing filter, or filter list out of range: %s", str(e))
logger.exception(e)
def collectPropertiesEx(self, specList):
'''Preforms a full collection of the specList passed. Will not retain any information
or update status. Any filter created during this process is destroyed after collection.
this method will be most often used with buildFilterList.
'''
ro = Connection.vim25client.new('RetrieveOptions')
try:
props = self.targetCollector.retrievePropertiesEx(specSet=specList, options=ro)
yield props.objects
while hasattr(props, 'token'):
lastToken = str(props.token)
props = self.targetCollector.continueRetrievePropertiesEx(token=lastToken)
yield props.objects
except Exception as e:
logger.error("collect properties ex fail: %s", str(e))
logger.exception(e)
def checkForUpdates(self, ver=None, maxObjUpdatesWaitOp=None):
''' Used to check for any updated changes on ALL created filters. An optional version
can be specified to check for a difference between the specified version and the current
configuration. This method will return null if there are no differences between the
the specified versions. Submitting a version of "None" will cause a full dump of the filters
at the creation of the first checkForUpdates() call
'''
try:
lastVersion = ver
waitOptions = Connection.vim25client.new('WaitOptions', maxWaitSeconds=0, maxObjectUpdates=maxObjUpdatesWaitOp)
data = self.targetCollector.waitForUpdatesEx(version=ver, options=waitOptions)
if data:
lastVersion = getattr(data, 'version')
# Version may have _ hence trim it
if lastVersion.find('_') >= 0:
lastVersion = lastVersion[0:lastVersion.find('_')]
# When result set in chunks then we need to do this hack
# If there is subversion, it always start with oldversion_subversion and last truncated
# result data contains actual increased version so increasing version by 1 to have right
# count for all object
lastVersion = int(str(lastVersion)) + 1
yield lastVersion, data
while hasattr(data, 'truncated'):
version = data.version
data = self.targetCollector.waitForUpdatesEx(version=version, options=waitOptions)
# Version may have _, hence trim it, also increase by 1 as described above
if version.find('_') >= 0:
mainVer = version[0:version.find('_')]
mainVer = int(str(mainVer)) + 1
else:
mainVer = version
yield mainVer, data
except Exception as e:
logger.error("[Inventory] checkForUpdates Failure")
logger.exception(e)
def dumpCollector(self):
'''Preforms a full dump of the currently set fSpecList.
'''
ro = Connection.vim25client.new('RetrieveOptions')
try:
props = self.targetCollector.retrievePropertiesEx(specSet=self.fSpecList, options=ro)
return props
except Exception as e:
logger.error("dump collector took a dump: %s", str(e))
logger.exception(e)
# End of Class Definitions, starting collection functions.
def CreateHierarchyCollector(managedObjectReference=None, targetConfigObject=None, updateType="update", version=None, oneTime=False, addlTargetConfig=None):
''' Primary method to create collecting inventory. managedObjectReference is to reference a propertyCollector
that has already been created. You can pass the MOR value to this method, and it will create a reference
to that existing property collector. You do not need to specify targetEntityType when using a MOR reference.
For all new property collectors (like the first run), you should use targetEntityType. This will reference
the entity out of the targetConfig class for what properties to collect. updateType is to be used in
conjunction with an existing MOR, "update" will allow the user to submit a version for checking differences,
while "recycle" will destroy the property collector and then create a new property collector.
"addlTargetConfig" will include any additional properties that need to be collected, as specified in the conf file.
It also create filterspec and filter as well before return property collector object
Returns: version, property collector object, targetConfigObject (the returned configuration target for chaining to a formatter), mor (The returned MOR to
the created / updated property collector, version
'''
try:
logger.debug("[Inventory] CollectInventory called with these options: managedObjectReference:{0}, targetConfigObject:{1}, updateType:{2}, version:{3}, oneTime:{4}, addlTargetConfig: {5}".format(managedObjectReference, targetConfigObject, updateType, version, oneTime, addlTargetConfig))
configObject = getattr(targetConfig, targetConfigObject)
if addlTargetConfig:
configObject['HostSystem'].extend(addlTargetConfig)
logger.debug("[Inventory] CollectInventory: Addl properties added for 'HostSystem', configObject now contains:"+str(configObject))
if managedObjectReference:
logger.debug("[Inventory] Received an MOR. Rebuilding old collector.")
hierarchyCollector = PropCollector(mor=managedObjectReference)
if updateType=="recycle":
try:
logger.debug("[Inventory] Old collector being recycled")
hierarchyCollector.targetCollector.destroyPropertyCollector()
except Exception as wf:
if "Server raised fault:" in str(wf) and "has already been deleted or has not been completely created" in str(wf):
logger.debug("[Inventory] Destroy Collector called with a non-existing collector, assuming deleted and creating new.")
else:
raise wf
mor=None
version=None
hierarchyCollector = PropCollector()
mor = str(hierarchyCollector.targetCollector.getMOR().value)
logger.debug("[Inventory] Recycled collector MOR:{0}".format(mor))
hierarchyCollector.buildFilterSpecList(configObject)
hierarchyCollector.buildFilter(hierarchyCollector.fSpecList, partUpdates=True)
if oneTime == True:
logger.debug("[Inventory] OneTime collection is set to true with existing MOR.")
hierarchyCollector.buildFilterSpecList(configObject)
mor = str(hierarchyCollector.targetCollector.getMOR().value)
return version, hierarchyCollector, targetConfigObject, mor
else:
logger.debug("[Inventory] Creating new collector.")
hierarchyCollector = PropCollector()
mor = str(hierarchyCollector.targetCollector.getMOR().value)
logger.debug("[Inventory] New collector MOR:{0}".format(mor))
hierarchyCollector.buildFilterSpecList(configObject)
hierarchyCollector.buildFilter(hierarchyCollector.fSpecList, partUpdates=True)
return version, hierarchyCollector, targetConfigObject, mor
except Exception as e:
logger.error("error in creating hierarchy collector")
logger.exception(e)
raise e
def DestroyHierarchyCollector(hierarchyCollector):
'''
Destroy Property collector
@param hierarchyCollector: PropertyCollector object
'''
if hierarchyCollector is None or hierarchyCollector.targetCollector is None:
logger.error("Hierarchy collector can not be destroy as it is passed as null value.")
return
hierarchyCollector.targetCollector.destroyPropertyCollector()
def Jsonify(target):
try:
return json.dumps(target)
except Exception as e:
logger.error("Error trying to jsonify object")
logger.exception(e)
def FlattenCombinedData(data, version=None):
''' Takes a combinedData object from a processed dataSet and formats the output
for splunk.
'''
for filterItem in data.filterSet:
for objectItem in filterItem.objectSet:
tempFullObject=dict(utils.CheckAttribute(objectItem))
tempFullObject=utils.Folderize(tempFullObject)
if 'kind' in tempFullObject:
del tempFullObject['kind']
if 'obj' in tempFullObject:
tempFullObject['moid'] = tempFullObject['obj']['moid']
tempFullObject['type'] = tempFullObject['obj']['type']
tempFullObject['rootFolder'] = { 'moid':Connection.rootFolder.getMOR().value }
if version:
tempFullObject['collectionVersion'] = version
del tempFullObject['obj']
yield Jsonify(tempFullObject)