Skip to content

Commit

Permalink
Add VmomiSupport.VmomiJSONEncoder for encoding pyVmomi objects as JSON.
Browse files Browse the repository at this point in the history
  • Loading branch information
jeking3 committed Oct 25, 2018
1 parent aace16b commit 0c61f30
Show file tree
Hide file tree
Showing 18 changed files with 18,791 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
build
dist
.idea
.tox
*.iml
*.patch
*.diff
Expand Down
112 changes: 110 additions & 2 deletions pyVmomi/VmomiSupport.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# VMware vSphere Python SDK
# Copyright (c) 2008-2016 VMware, Inc. All Rights Reserved.
# Copyright (c) 2008-2018 VMware, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,6 +27,7 @@
from datetime import datetime
from pyVmomi import Iso8601
import base64
import json
import threading
if PY3:
from functools import cmp_to_key
Expand Down Expand Up @@ -273,6 +274,106 @@ def __call__(self, **kwargs):
else:
raise AttributeError("'%s' does not exist" % self.name)


class VmomiJSONEncoder(json.JSONEncoder):
"""
Custom JSON encoder to encode vSphere objects.
When a ManagedObject is encoded, it gains three properties:
_vimid is the _moId (ex: 'vm-42')
_vimref is the moRef (ex: 'vim.VirtualMachine:vm-42')
_vimtype is the class name (ex: 'vim.VirtualMachine')
@example "Explode only the object passed in"
data = json.dumps(vm, cls=VmomiJSONEncoder)
@example "Explode specific objects"
data = json.dumps(vm, cls=VmomiJSONEncoder,
explode=[vm, vm.network[0]])
@example "Explode all virtual machines in a list and their snapshots"
data = json.dumps([vm1, vm2], cls=VmomiJSONEncoder,
explode=[templateOf('VirtualMachine'),
templateOf('VirtualMachineSnapshot')])
"""
def __init__(self, *args, **kwargs):
"""
Consumer must specify what types to explode into full content
and what types to leave as a ManagedObjectReference. If the
root object of the encoding is a ManagedObject, it will be
added to the explode list automatically.
A ManagedObject is only exploded once, the first time it is
encountered. All subsequent times it will be a moRef.
@param explode (list) A list of objects and/or types to
expand in the conversion process. Directly add any
objects to the list, or add the type of any object
using type(templateOf('VirtualMachine')) for example.
"""
self._done = set()
self._first = True
self._explode = kwargs.pop('explode', None)
super(VmomiJSONEncoder, self).__init__(*args, **kwargs)

def default(self, obj): # pylint: disable=method-hidden
if self._first:
self._first = False
if not self._explode:
self._explode = []
if isinstance(obj, ManagedObject):
self._explode.append(obj)
if isinstance(obj, ManagedObject):
if self.explode(obj):
result = {}
result['_vimid'] = obj._moId
result['_vimref'] = '{}:{}'.format(obj.__class__.__name__,
obj._moId)
result['_vimtype'] = obj.__class__.__name__
for prop in obj._GetPropertyList():
result[prop.name] = getattr(obj, prop.name)
return result
return str(obj).strip("'") # see FormatObject below
if isinstance(obj, DataObject):
return obj.__dict__
if isinstance(obj, binary):
result = base64.b64encode(obj)
if PY3: # see FormatObject below
result = str(result, 'utf-8')
return result
if isinstance(obj, datetime):
return Iso8601.ISO8601Format(obj)
if isinstance(obj, UncallableManagedMethod):
return obj.name
if isinstance(obj, ManagedMethod):
return str(obj) # see FormatObject below
if isinstance(obj, type):
return obj.__name__
super(VmomiJSONEncoder, self).default(obj)

def explode(self, obj):
""" Determine if the object should be exploded. """
if obj in self._done:
return False
result = False
for item in self._explode:
if hasattr(item, '_moId'):
# If it has a _moId it is an instance
if obj._moId == item._moId:
result = True
else:
# If it does not have a _moId it is a template
if obj.__class__.__name__ == item.__name__:
result = True
if result:
self._done.add(obj)
return result


def templateOf(typestr):
""" Returns a class template. """
return GetWsdlType(XMLNS_VMODL_BASE, typestr)

## Format a python VMOMI object
#
# @param val the object
Expand Down Expand Up @@ -1325,7 +1426,14 @@ def GetNamespace(self, vmodlNs):
if PY3:
long = type("long", (int,), {})
URI = type("URI", (str,), {})
binary = type("binary", (binary_type,), {})
if not PY3:
# six defines binary_type in python2 as a string; this means the
# JSON encoder sees checksum properties as strings and attempts
# to perform utf-8 decoding on them because they contain high-bit
# characters.
binary = type("binary", (bytearray,), {})
else:
binary = type("binary", (binary_type,), {})
PropertyPath = type("PropertyPath", (text_type,), {})

# _wsdlTypeMapNSs store namespaces added to _wsdlTypeMap in _SetWsdlType
Expand Down
86 changes: 86 additions & 0 deletions sample/dumpjson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python
# VMware vSphere Python SDK
# Copyright (c) 2008-2018 VMware, Inc. All Rights Reserved.
#
# Licensed 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.

"""
Python program for dumping JSON of any object given the type and MOID
"""

from __future__ import print_function

from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim, VmomiSupport

import argparse
import atexit
import getpass
import json
import ssl

def GetArgs():
"""
Supports the command-line arguments listed below.
"""
parser = argparse.ArgumentParser(
description='Process args for extracting JSON from a Managed Object')
parser.add_argument('-s', '--host', required=True, action='store',
help='Remote host to connect to')
parser.add_argument('-o', '--port', type=int, default=443, action='store',
help='Port to connect on')
parser.add_argument('-u', '--user', required=True, action='store',
help='User name to use when connecting to host')
parser.add_argument('-p', '--password', required=False, action='store',
help='Password to use when connecting to host')
parser.add_argument('-t', '--type', required=True, action='store',
help='The vim type lookup, ex: "VirtualMachine"')
parser.add_argument('-i', '--id', required=True, action='store',
help='The MOID to lookup, ex: "vm-42"')
args = parser.parse_args()
return args

def main():
"""
Simple command-line program for dumping the contents of any managed object.
"""

args = GetArgs()
if args.password:
password = args.password
else:
password = getpass.getpass(prompt='Enter password for host %s and '
'user %s: ' % (args.host,args.user))

context = None
if hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context()
si = SmartConnect(host=args.host,
user=args.user,
pwd=password,
port=int(args.port),
sslContext=context)
if not si:
print("Could not connect to the specified host using specified "
"username and password")
return -1

atexit.register(Disconnect, si)

obj = VmomiSupport.templateOf(args.type)(args.id, si._stub)
print(json.dumps(obj, cls=VmomiSupport.VmomiJSONEncoder,
sort_keys=True, indent=4))

# Start program
if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions tests/files/test_json_datacenter_explode.expect

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/files/test_json_datastore_explode.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_vimid": "datastore-15", "_vimref": "vim.Datastore:datastore-15", "_vimtype": "vim.Datastore", "alarmActionsEnabled": true, "availableField": [], "browser": "vim.host.DatastoreBrowser:datastoreBrowser-datastore-15", "capability": {"directoryHierarchySupported": true, "dynamicProperty": [], "dynamicType": null, "nativeSnapshotSupported": false, "perFileThinProvisioningSupported": true, "rawDiskMappingsSupported": true, "seSparseSupported": true, "storageIORMSupported": true, "topLevelDirectoryCreateSupported": true, "upitSupported": null, "vmdkExpandSupported": true, "vmfsSparseSupported": false, "vsanSparseSupported": false}, "configIssue": [], "configStatus": "gray", "customValue": [], "declaredAlarmState": [{"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-110", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-110.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353264Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-24", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-24.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353397Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-28", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-28.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353475Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-29", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-29.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353541Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-53", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-53.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353598Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-7", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-7.datastore-15", "overallStatus": "green", "time": "2018-09-19T18:00:22.353783Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-79", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-79.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.353844Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-80", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-80.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.356378Z"}, {"acknowledged": false, "acknowledgedByUser": null, "acknowledgedTime": null, "alarm": "vim.alarm.Alarm:alarm-84", "dynamicProperty": [], "dynamicType": null, "entity": "vim.Datastore:datastore-15", "eventKey": null, "key": "alarm-84.datastore-15", "overallStatus": "gray", "time": "2018-09-19T18:00:22.356497Z"}], "disabledMethod": ["DatastoreExitMaintenanceMode_Task"], "effectiveRole": [-1], "host": [{"dynamicProperty": [], "dynamicType": null, "key": "vim.HostSystem:host-14", "mountInfo": {"accessMode": "readWrite", "accessible": true, "dynamicProperty": [], "dynamicType": null, "inaccessibleReason": null, "mounted": true, "path": "/vmfs/volumes/5b7ee5c9-4e8f1921-93b3-90b11c51ac73"}}], "info": {"aliasOf": null, "containerId": null, "dynamicProperty": [], "dynamicType": null, "freeSpace": 177978998784, "lun": [], "maxFileSize": 70368744177664, "maxMemoryFileSize": 70368744177664, "maxPhysicalRDMFileSize": 70368744177664, "maxVirtualDiskCapacity": 68169720922112, "maxVirtualRDMFileSize": 68169720922112, "name": "datastore1", "timestamp": "2018-10-25T00:07:06.456385Z", "url": "ds:///vmfs/volumes/5b7ee5c9-4e8f1921-93b3-90b11c51ac73/", "vmfs": {"blockSize": 1024, "blockSizeMb": 1, "capacity": 590826438656, "dynamicProperty": [], "dynamicType": null, "extent": [{"diskName": "naa.6b8ca3a0e65228002312a2e3098048bf", "dynamicProperty": [], "dynamicType": null, "partition": 3}], "forceMountedInfo": null, "local": true, "majorVersion": 6, "maxBlocks": 63963136, "name": "datastore1", "scsiDiskType": null, "ssd": false, "type": "VMFS", "unmapBandwidthSpec": null, "unmapGranularity": 1024, "unmapPriority": "low", "uuid": "5b7ee5c9-4e8f1921-93b3-90b11c51ac73", "version": "6.82", "vmfsUpgradable": false}}, "iormConfiguration": {"congestionThreshold": 30, "congestionThresholdMode": "automatic", "dynamicProperty": [], "dynamicType": null, "enabled": false, "percentOfPeakThroughput": 90, "reservableIopsThreshold": null, "reservationEnabled": true, "statsAggregationDisabled": false, "statsCollectionEnabled": false}, "name": "datastore1", "overallStatus": "green", "parent": "vim.Folder:group-s5", "permission": [], "recentTask": [], "summary": {"accessible": true, "capacity": 590826438656, "datastore": "vim.Datastore:datastore-15", "dynamicProperty": [], "dynamicType": null, "freeSpace": 177978998784, "maintenanceMode": "normal", "multipleHostAccess": false, "name": "datastore1", "type": "VMFS", "uncommitted": 335226992919, "url": "ds:///vmfs/volumes/5b7ee5c9-4e8f1921-93b3-90b11c51ac73/"}, "tag": [], "triggeredAlarmState": [], "value": [], "vm": ["vim.VirtualMachine:vm-127", "vim.VirtualMachine:vm-69", "vim.VirtualMachine:vm-17", "vim.VirtualMachine:vm-42", "vim.VirtualMachine:vm-18", "vim.VirtualMachine:vm-36", "vim.VirtualMachine:vm-19", "vim.VirtualMachine:vm-21", "vim.VirtualMachine:vm-20", "vim.VirtualMachine:vm-22", "vim.VirtualMachine:vm-32"]}
1 change: 1 addition & 0 deletions tests/files/test_json_host_explode.expect

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/files/test_json_network_explode.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_vimid": "network-16", "_vimref": "vim.Network:network-16", "_vimtype": "vim.Network", "alarmActionsEnabled": true, "availableField": [], "configIssue": [], "configStatus": "green", "customValue": [], "declaredAlarmState": [], "disabledMethod": [], "effectiveRole": [-1], "host": ["vim.HostSystem:host-14", "vim.HostSystem:host-25"], "name": "VM Network", "overallStatus": "green", "parent": "vim.Folder:group-n6", "permission": [], "recentTask": [], "summary": {"accessible": true, "dynamicProperty": [], "dynamicType": null, "ipPoolId": null, "ipPoolName": "", "name": "VM Network", "network": "vim.Network:network-16"}, "tag": [], "triggeredAlarmState": [], "value": [], "vm": ["vim.VirtualMachine:vm-17", "vim.VirtualMachine:vm-18", "vim.VirtualMachine:vm-19", "vim.VirtualMachine:vm-20", "vim.VirtualMachine:vm-21", "vim.VirtualMachine:vm-22", "vim.VirtualMachine:vm-27", "vim.VirtualMachine:vm-28", "vim.VirtualMachine:vm-29", "vim.VirtualMachine:vm-32", "vim.VirtualMachine:vm-36", "vim.VirtualMachine:vm-42", "vim.VirtualMachine:vm-69", "vim.VirtualMachine:vm-86", "vim.VirtualMachine:vm-111", "vim.VirtualMachine:vm-127"]}
1 change: 1 addition & 0 deletions tests/files/test_json_vm_explode_default.expect

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/files/test_json_vm_explode_objs_match.expect

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/files/test_json_vm_explode_type_match.expect

Large diffs are not rendered by default.

Loading

0 comments on commit 0c61f30

Please sign in to comment.