I have a class which provides various methods for retrieving information from a VMware vSphere environment.
Over the time my class got extended and became large, so I thought I should spend some time to re-design it.
Currently I am thinking of re-designing a bit in that class and move/group related methods into separate modules as this would help with keeping the whole project modular and better organized as various functionality would be covered by dedicated modules.
All my class methods rely on an attribute which is performing the connection and interaction with the VMware vSphere SDK, so in order to move my methods into dedicated modules as functions I need to make sure to pass that attribute as well.
In order to get rid of the instance methods, I was thinking of using a decorator for my functions that would simply register functions as available tasks in a class attribute, which in turn would be available to instances of that class and can be called to perform the required operation.
Now I have this updated version of my class + a decorator to be used when creating new vSphere tasks.
"""
vPoller Agent module for the VMware vSphere Poller
vPoller Agents are used by the vPoller Workers, which take care of
establishing the connection to the vSphere hosts and do all the heavy lifting.
Check the vSphere Web Services SDK API for more information on the properties
you can request for any specific vSphere managed object
- https://www.vmware.com/support/developer/vc-sdk/
"""
import logging
from functools import wraps
from vconnector.core import VConnector
from vpoller.client.core import VPollerClientMessage
__all__ = ['VSphereAgent', 'task']
class VSphereAgent(VConnector):
"""
VSphereAgent class
Defines methods for retrieving vSphere object properties
These are the worker agents that do the actual
polling from the VMware vSphere host.
Extends:
VConnector
"""
_tasks = {}
@classmethod
def _add_task(cls, name, function, required):
cls._tasks[name] = {
'function': function,
'required': required,
}
def call_task(self, name, *args, **kwargs):
"""
Execute a vPoller task request
Args:
name (str): Name of the task to be executed
"""
if name not in self._tasks:
return {'success': 1, 'msg': 'Unknown task requested'}
return self._tasks[name]['function'](self, *args, **kwargs)
def _discover_objects(self, properties, obj_type):
"""
Helper method to simplify discovery of vSphere managed objects
This method is used by the '*.discover' vPoller Worker methods and is
meant for collecting properties for multiple objects at once, e.g.
during object discovery operation.
Args:
properties (list): List of properties to be collected
obj_type (pyVmomi.vim.*): Type of vSphere managed object
Returns:
The discovered objects in JSON format
"""
logging.info(
'[%s] Discovering %s managed objects',
self.host,
obj_type.__name__
)
view_ref = self.get_container_view(obj_type=[obj_type])
try:
data = self.collect_properties(
view_ref=view_ref,
obj_type=obj_type,
path_set=properties
)
except Exception as e:
return {'success': 1, 'msg': 'Cannot collect properties: %s' % e}
view_ref.DestroyView()
result = {
'success': 0,
'msg': 'Successfully discovered objects',
'result': data,
}
logging.debug(
'[%s] Returning result from operation: %s',
self.host,
result
)
return result
def _get_object_properties(self,
properties,
obj_type,
obj_property_name,
obj_property_value,
include_mors=False):
"""
Helper method to simplify retrieving of properties
This method is used by the '*.get' vPoller Worker methods and is
meant for collecting properties for a single managed object.
We first search for the object with property name and value,
then create a list view for this object and
finally collect it's properties.
Args:
properties (list): List of properties to be collected
obj_type pyVmomi.vim.*): Type of vSphere managed object
obj_property_name (str): Property name used for searching for the object
obj_property_value (str): Property value identifying the object in question
Returns:
The collected properties for this managed object in JSON format
"""
logging.info(
'[%s] Retrieving properties for %s managed object of type %s',
self.host,
obj_property_value,
obj_type.__name__
)
# Find the Managed Object reference for the requested object
try:
obj = self.get_object_by_property(
property_name=obj_property_name,
property_value=obj_property_value,
obj_type=obj_type
)
except Exception as e:
return {'success': 1, 'msg': 'Cannot collect properties: %s' % e}
if not obj:
return {
'success': 1,
'msg': 'Cannot find object %s' % obj_property_value
}
# Create a list view for this object and collect properties
view_ref = self.get_list_view(obj=[obj])
try:
data = self.collect_properties(
view_ref=view_ref,
obj_type=obj_type,
path_set=properties,
include_mors=include_mors
)
except Exception as e:
return {'success': 1, 'msg': 'Cannot collect properties: %s' % e}
view_ref.DestroyView()
result = {
'success': 0,
'msg': 'Successfully retrieved object properties',
'result': data,
}
logging.debug(
'[%s] Returning result from operation: %s',
self.host,
result
)
return result
def _object_datastore_get(self, obj_type, name):
"""
Helper method used for getting the datastores available to an object
This method searches for the managed object with 'name' and retrieves
the 'datastore' property which contains all datastores available/used
by the managed object, e.g. VirtualMachine, HostSystem.
Args:
obj_type (pyVmomi.vim.*): Managed object type
name (str): Name of the managed object, e.g. host, vm
Returns:
The discovered objects in JSON format
"""
logging.debug(
'[%s] Getting datastores for %s managed object of type %s',
self.host,
name,
obj_type.__name__
)
# Find the object by it's 'name' property
# and get the datastores available/used by it
data = self._get_object_properties(
properties=['datastore'],
obj_type=obj_type,
obj_property_name='name',
obj_property_value=name
)
if data['success'] != 0:
return data
# Get the name and datastore properties from the result
props = data['result'][0]
obj_datastores = props['datastore']
# Get a list view of the datastores available/used by
# this object and collect properties
view_ref = self.get_list_view(obj=obj_datastores)
result = self.collect_properties(
view_ref=view_ref,
obj_type=pyVmomi.vim.Datastore,
path_set=['name', 'info.url']
)
view_ref.DestroyView()
r = {
'success': 0,
'msg': 'Successfully discovered objects',
'result': result,
}
logging.debug('[%s] Returning result from operation: %s', self.host, r)
return r
def _object_alarm_get(self,
obj_type,
obj_property_name,
obj_property_value):
"""
Helper method for retrieving alarms for a single Managed Object
Args:
obj_type (pyVmomi.vim.*): Type of the Managed Object
obj_property_name (str): Property name used for searching for the object
obj_property_value (str): Property value identifying the object in question
Returns:
The triggered alarms for the Managed Object
"""
logging.debug(
'[%s] Retrieving alarms for %s managed object of type %s',
self.host,
obj_property_value,
obj_type.__name__
)
# Get the 'triggeredAlarmState' property for the managed object
data = self._get_object_properties(
properties=['triggeredAlarmState'],
obj_type=obj_type,
obj_property_name=obj_property_name,
obj_property_value=obj_property_value
)
if data['success'] != 0:
return data
result = []
props = data['result'][0]
alarms = props['triggeredAlarmState']
for alarm in alarms:
a = {
'key': str(alarm.key),
'info': alarm.alarm.info.name,
'time': str(alarm.time),
'entity': alarm.entity.name,
'acknowledged': alarm.acknowledged,
'overallStatus': alarm.overallStatus,
'acknowledgedByUser': alarm.acknowledgedByUser,
}
result.append(a)
r = {
'success': 0,
'msg': 'Successfully retrieved alarms',
'result': result,
}
logging.debug(
'[%s] Returning result from operation: %s',
self.host,
r
)
return r
def task(name, required):
"""
Decorator for creating new vPoller tasks
Args:
name (str): Name of the vPoller task
required (list): A list of required message attributes
"""
def decorator(function):
logging.debug(
'Creating task %s at %s, requiring %s',
name,
function,
required
)
@wraps(function)
def wrapper(*args, **kwargs):
#
# TODO: Validate message before processing
#
return function(*args, **kwargs)
VSphereAgent._add_task(
name=name,
function=wrapper,
required=required
)
return wrapper
return decorator
I have already moved my vSphere Datacenter
related methods as tasks using the @vpoller.agent.core.task
decorator as shown below.
"""
vSphere Agent Datacenter Tasks
https://github.com/dnaeon/py-vpoller/tree/decorators/src/vpoller
"""
import pyVmomi
from vpoller.agent.core import task
@task(name='datacenter.discover', required=['hostname', 'name'])
def datacenter_discover(agent, msg):
"""
Discover all vim.Datacenter managed objects
Example client message would be:
{
"method": "datacenter.discover",
"hostname": "vc01.example.org",
}
Example client message which also requests additional properties:
{
"method": "datacenter.discover",
"hostname": "vc01.example.org",
"properties": [
"name",
"overallStatus"
]
}
Returns:
The discovered objects in JSON format
"""
# Property names to be collected
properties = ['name']
if 'properties' in msg and msg['properties']:
properties.extend(msg['properties'])
r = agent._discover_objects(
properties=properties,
obj_type=pyVmomi.vim.Datacenter
)
return r
@task(name='datacenter.get', required=['hostname', 'name'])
def datacenter_get(agent, msg):
"""
Get properties of a single vim.Datacenter managed object
Example client message would be:
{
"method": "datacenter.get",
"hostname": "vc01.example.org",
"name": "MyDatacenter",
"properties": [
"name",
"overallStatus"
]
}
Returns:
The managed object properties in JSON format
"""
# Property names to be collected
properties = ['name']
if 'properties' in msg and msg['properties']:
properties.extend(msg['properties'])
return agent._get_object_properties(
properties=properties,
obj_type=pyVmomi.vim.Datacenter,
obj_property_name='name',
obj_property_value=msg['name']
)
@task(name='datacenter.alarm.get', required=['hostname', 'name'])
def datacenter_alarm_get(agent, msg):
"""
Get all alarms for a vim.Datacenter managed object
Example client message would be:
{
"method": "datacenter.alarm.get",
"hostname": "vc01.example.org",
"name": "MyDatacenter"
}
Returns:
The discovered alarms in JSON format
"""
result = agent._object_alarm_get(
obj_type=pyVmomi.vim.Datacenter,
obj_property_name='name',
obj_property_value=msg['name']
)
return result
Using the @vpoller.agent.core.task
decorator I am now able to create new tasks in dedicated modules, which is what the main goal was.
My concerns with this approach is whether this is Pythonic (passing around self
to external functions) and whether there is a better way to do it.