-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
get_properties: property collector action
- Loading branch information
Showing
4 changed files
with
338 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# Licensed to the StackStorm, Inc ('StackStorm') under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from pyVmomi import vim, vmodl # pylint: disable-msg=E0611 | ||
from vmwarelib.actions import BaseAction | ||
|
||
import copy | ||
import datetime | ||
import json | ||
import pyVmomi | ||
|
||
|
||
class GetProperties(BaseAction): | ||
|
||
def run(self, type, property, id, raw, vsphere=None): | ||
""" | ||
Leverage the Property Collector to retrieve properties from any | ||
Managed Object. | ||
Args: | ||
- type: vimType | ||
- properties: optional array of properties to get (default: all) | ||
- ids: optional array of MOIDs to limit results (default: all) | ||
- vsphere: pre-configured connection information | ||
Returns: | ||
- dict: key = moid, value = dict of properties | ||
""" | ||
|
||
self.establish_connection(vsphere) | ||
return self.collect(self.si_content, type, property, id, raw) | ||
|
||
def collect(self, content, type, properties, ids, raw): | ||
""" | ||
Leverage the Property Collector to retrieve properties from any | ||
Managed Object. | ||
Args: | ||
- content: service instance content | ||
- type: object type | ||
- properties: optional array of properties to get (default: all) | ||
- ids: optional array of MOIDs to limit results (default: all) | ||
Returns: | ||
- dict: key = moid, value = dict of properties | ||
""" | ||
|
||
vimtype = getattr(vim, type) | ||
|
||
rootFolder = content.rootFolder | ||
viewMgr = content.viewManager | ||
if not ids: | ||
view = viewMgr.CreateContainerView(container=rootFolder, | ||
type=[vimtype], | ||
recursive=True) | ||
else: | ||
view = viewMgr.CreateListView() | ||
for id in ids: | ||
view.ModifyListView(add=[ | ||
pyVmomi.VmomiSupport.GetWsdlType('urn:vim25', type)(id)]) | ||
|
||
traversal_spec = vmodl.query.PropertyCollector.TraversalSpec() | ||
traversal_spec.name = 'traverseEntities' | ||
traversal_spec.path = 'view' | ||
traversal_spec.skip = False | ||
traversal_spec.type = view.__class__ | ||
|
||
obj_spec = vmodl.query.PropertyCollector.ObjectSpec() | ||
obj_spec.obj = view | ||
obj_spec.skip = True | ||
obj_spec.selectSet = [traversal_spec] | ||
|
||
property_spec = vmodl.query.PropertyCollector.PropertySpec() | ||
property_spec.type = vimtype | ||
if not properties: | ||
property_spec.all = True | ||
else: | ||
property_spec.pathSet = properties | ||
|
||
filter_spec = vmodl.query.PropertyCollector.FilterSpec() | ||
filter_spec.objectSet = [obj_spec] | ||
filter_spec.propSet = [property_spec] | ||
|
||
rawdata = content.propertyCollector.RetrieveContents([filter_spec]) | ||
return self.transform(ids, rawdata) if not raw else rawdata | ||
|
||
def jsonify_vsphere_obj(self, obj): | ||
"""JSONify a vSphere Managed/Data object.""" | ||
class PyVmomiObjectJSONEncoder(json.JSONEncoder): | ||
"""Custom JSON encoder to encode vSphere object.""" | ||
def __init__(self, *args, **kwargs): | ||
super(PyVmomiObjectJSONEncoder, self).__init__(*args, **kwargs) | ||
|
||
def default(self, obj): # pylint: disable=method-hidden | ||
if isinstance(obj, datetime.datetime): | ||
return pyVmomi.Iso8601.ISO8601Format(obj) | ||
elif isinstance(obj, pyVmomi.VmomiSupport.DataObject): | ||
# eliminate the very annoying Dynamic fields if empty | ||
if ( | ||
obj.__dict__['dynamicType'] is None and | ||
len(obj.__dict__['dynamicProperty']) == 0 | ||
): | ||
tmp = copy.deepcopy(obj.__dict__) | ||
tmp.pop('dynamicType') | ||
tmp.pop('dynamicProperty') | ||
return tmp | ||
return obj.__dict__ | ||
elif isinstance(obj, pyVmomi.VmomiSupport.ManagedObject): | ||
return unquote(obj).split(':')[-1] | ||
elif isinstance(obj, type): | ||
return str(obj) | ||
return json.JSONEncoder.default(self, obj) | ||
return json.loads(PyVmomiObjectJSONEncoder().encode(obj)) | ||
|
||
def transform(self, ids, rawdata): | ||
result = {} | ||
for obj in rawdata: | ||
objid = unquote(obj.obj).split(':')[-1] | ||
ps = {} | ||
for prop in obj.propSet: | ||
ps[unquote(prop.name)] = self.jsonify_vsphere_obj(prop.val) | ||
result[objid] = ps | ||
return (not ids or sorted(result.keys()) == sorted(ids), result) | ||
|
||
|
||
def unquote(item): | ||
return str(item).strip("'") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
name: get_properties | ||
runner_type: python-script | ||
description: Get properties of managed objects through the PropertyCollector. | ||
enabled: true | ||
entry_point: get_properties.py | ||
parameters: | ||
type: | ||
type: string | ||
description: The type of object to get. This type must be compatible with CreateContainerView. | ||
required: true | ||
position: 0 | ||
enum: | ||
- ComputeResource | ||
- Datacenter | ||
- Datastore | ||
- Folder | ||
- HostSystem | ||
- ManagedEntity | ||
- Network | ||
- ResourcePool | ||
- VirtualMachine | ||
id: | ||
type: array | ||
description: MOIDs to restrict the results to. Omit to retrieve all objects. | ||
required: false | ||
position: 1 | ||
property: | ||
type: array | ||
description: Fully qualified property within the type (dot separated), such as 'config.hardware.memoryMB' for a VirtualMachine. Omit to retrieve all properties. | ||
required: false | ||
position: 2 | ||
raw: | ||
type: boolean | ||
description: If True, return the raw result of the PropertyCollector.RetrieveContents call. | ||
required: false | ||
default: false | ||
position: 3 | ||
vsphere: | ||
type: string | ||
description: Pre-Configured vsphere connection details | ||
required: false | ||
position: 4 | ||
default: ~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# Licensed to the StackStorm, Inc ('StackStorm') under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
|
||
import copy | ||
import datetime | ||
import mock | ||
import pyVmomi | ||
|
||
from get_properties import GetProperties | ||
from pyVmomi import vim # pylint: disable-msg=E0611 | ||
from vsphere_base_action_test_case import VsphereBaseActionTestCase | ||
|
||
__all__ = [ | ||
'GetPropertiesTestCase' | ||
] | ||
|
||
|
||
class GetPropertiesTestCase(VsphereBaseActionTestCase): | ||
__test__ = True | ||
action_cls = GetProperties | ||
|
||
transformed = { | ||
"vm-22": { | ||
"config.hardware.memoryMB": 10240, | ||
"dataobj-dyn": { | ||
"arguments": "arguments", | ||
'dynamicType': "Hello", | ||
'dynamicProperty': [], | ||
"envVariables": ["A=B", "C=D"], | ||
"programPath": "cmd.exe", | ||
"workingDirectory": "/tmp" | ||
}, | ||
"guest.ipAddress": "10.99.0.4", | ||
"name": "VCSA", | ||
"network": ["network-15"], | ||
"time": "1992-01-12T01:01:22Z" | ||
}, | ||
"vm-46": { | ||
"config.hardware.memoryMB": 2048, | ||
"dataobj-nodyn": { | ||
"arguments": "arguments", | ||
"envVariables": ["A=B", "C=D"], | ||
"programPath": "cmd.exe", | ||
"workingDirectory": "/tmp" | ||
}, | ||
"guest.ipAddress": "fe80::250:56ff:feb4:cfb9", | ||
"name": "testvm", | ||
"network": [], | ||
"time": "1992-01-17T01:01:46Z" | ||
} | ||
} | ||
|
||
def setUp(self): | ||
def mockDynamicProperty(name, val): | ||
result = mock.Mock() | ||
result.name = name | ||
result.val = val | ||
return result | ||
super(GetPropertiesTestCase, self).setUp() | ||
self._action = self.get_action_instance(self.new_config) | ||
self._action.establish_connection = mock.Mock() | ||
self._action.si_content = mock.Mock() | ||
self._action.si_content.rootFolder = mock.Mock() | ||
self._action.si_content.viewManager = mock.Mock() | ||
self._action.si_content.viewManager.CreateContainerView = mock.Mock() | ||
self._action.si_content.viewManager.CreateListView = mock.Mock() | ||
self._action.si_content.propertyCollector = mock.Mock() | ||
cmdspec22 = vim.vm.guest.ProcessManager.ProgramSpec( | ||
arguments="arguments", | ||
envVariables=["A=B", "C=D"], | ||
programPath="cmd.exe", | ||
workingDirectory='/tmp') | ||
cmdspec46 = copy.deepcopy(cmdspec22) | ||
cmdspec22.__dict__['dynamicType'] = 'Hello' | ||
# mock up the raw objects used to test transform | ||
vm22 = mock.Mock() | ||
vm22.obj = 'vim.VirtualMachine:vm-22' | ||
vm22.propSet = [mockDynamicProperty("config.hardware.memoryMB", 10240), | ||
mockDynamicProperty("dataobj-dyn", cmdspec22), | ||
mockDynamicProperty("guest.ipAddress", "10.99.0.4"), | ||
mockDynamicProperty("name", "VCSA"), | ||
mockDynamicProperty("network", [ | ||
pyVmomi.VmomiSupport.GetWsdlType( | ||
'urn:vim25', 'Network')( | ||
'network-15')]), | ||
mockDynamicProperty("time", | ||
datetime.datetime( | ||
1992, 1, 12, 1, 1, 22))] | ||
vm46 = mock.Mock() | ||
vm46.obj = 'vim.VirtualMachine:vm-46' | ||
vm46.propSet = [mockDynamicProperty("config.hardware.memoryMB", 2048), | ||
mockDynamicProperty("dataobj-nodyn", cmdspec46), | ||
mockDynamicProperty("guest.ipAddress", | ||
"fe80::250:56ff:feb4:cfb9"), | ||
mockDynamicProperty("name", "testvm"), | ||
mockDynamicProperty("network", []), | ||
mockDynamicProperty("time", | ||
datetime.datetime( | ||
1992, 1, 17, 1, 1, 46))] | ||
self.raw = [vm22, vm46] | ||
|
||
@mock.patch('pyVmomi.vmodl.query.PropertyCollector') | ||
def test_simple_property_by_id(self, pc): | ||
with mock.patch.object( | ||
self._action.si_content.propertyCollector, 'RetrieveContents', | ||
return_value=self.raw): | ||
result = self._action.run(type='VirtualMachine', | ||
id=['vm-22', 'vm-46'], | ||
property=['does.not.exist'], raw=False) | ||
assert self._action.si_content.viewManager.CreateListView.called | ||
# status True because every object was found | ||
self.assertEqual(result, (True, self.transformed)) | ||
with mock.patch.object( | ||
self._action.si_content.propertyCollector, 'RetrieveContents', | ||
return_value=self.raw): | ||
result = self._action.run(type='VirtualMachine', | ||
id=['vm-22', 'vm-46', 'vm-47'], | ||
property=None, raw=False) | ||
# status False because not every object was found | ||
self.assertEqual(result, (False, self.transformed)) | ||
|
||
@mock.patch('pyVmomi.vmodl.query.PropertyCollector') | ||
def test_simple_by_type(self, pc): | ||
with mock.patch.object( | ||
self._action.si_content.propertyCollector, 'RetrieveContents', | ||
return_value=self.raw): | ||
result = self._action.run(type='VirtualMachine', id=None, | ||
property=None, raw=False) | ||
assert\ | ||
self._action.si_content.viewManager.CreateContainerView.called | ||
self.assertEqual(result, (True, self.transformed)) | ||
|
||
@mock.patch('pyVmomi.vmodl.query.PropertyCollector') | ||
def test_raw_output(self, pc): | ||
with mock.patch.object( | ||
self._action.si_content.propertyCollector, 'RetrieveContents', | ||
return_value=self.raw): | ||
result = self._action.run(type='VirtualMachine', id=None, | ||
property=None, raw=True) | ||
self.assertNotEqual(result, self.transformed) |