Add ability to get vmware_guest_facts using vsphere schema output (#47446)

pull/52489/head
James E. King III 6 years ago committed by ansibot
parent 7f50f467fe
commit f2495ef0d3

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com> # Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# Copyright: (c) 2018, James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
import atexit import atexit
import ansible.module_utils.common._collections_compat as collections_compat
import json
import os import os
import re import re
import ssl import ssl
@ -27,11 +30,13 @@ except ImportError:
PYVMOMI_IMP_ERR = None PYVMOMI_IMP_ERR = None
try: try:
from pyVim import connect from pyVim import connect
from pyVmomi import vim, vmodl from pyVmomi import vim, vmodl, VmomiSupport
HAS_PYVMOMI = True HAS_PYVMOMI = True
HAS_PYVMOMIJSON = hasattr(VmomiSupport, 'VmomiJSONEncoder')
except ImportError: except ImportError:
PYVMOMI_IMP_ERR = traceback.format_exc() PYVMOMI_IMP_ERR = traceback.format_exc()
HAS_PYVMOMI = False HAS_PYVMOMI = False
HAS_PYVMOMIJSON = False
from ansible.module_utils._text import to_text, to_native from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.six import integer_types, iteritems, string_types, raise_from from ansible.module_utils.six import integer_types, iteritems, string_types, raise_from
@ -1274,3 +1279,104 @@ class PyVmomi(object):
return f return f
self.module.fail_json(msg="No vmdk file found for path specified [%s]" % vmdk_path) self.module.fail_json(msg="No vmdk file found for path specified [%s]" % vmdk_path)
#
# Conversion to JSON
#
def _deepmerge(self, d, u):
"""
Deep merges u into d.
Credit:
https://bit.ly/2EDOs1B (stackoverflow question 3232943)
License:
cc-by-sa 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
Changes:
using collections_compat for compatibility
Args:
- d (dict): dict to merge into
- u (dict): dict to merge into d
Returns:
dict, with u merged into d
"""
for k, v in iteritems(u):
if isinstance(v, collections_compat.Mapping):
d[k] = self._deepmerge(d.get(k, {}), v)
else:
d[k] = v
return d
def _extract(self, data, remainder):
"""
This is used to break down dotted properties for extraction.
Args:
- data (dict): result of _jsonify on a property
- remainder: the remainder of the dotted property to select
Return:
dict
"""
result = dict()
if '.' not in remainder:
result[remainder] = data[remainder]
return result
key, remainder = remainder.split('.', 1)
result[key] = self._extract(data[key], remainder)
return result
def _jsonify(self, obj):
"""
Convert an object from pyVmomi into JSON.
Args:
- obj (object): vim object
Return:
dict
"""
return json.loads(json.dumps(obj, cls=VmomiSupport.VmomiJSONEncoder,
sort_keys=True, strip_dynamic=True))
def to_json(self, obj, properties=None):
"""
Convert a vSphere (pyVmomi) Object into JSON. This is a deep
transformation. The list of properties is optional - if not
provided then all properties are deeply converted. The resulting
JSON is sorted to improve human readability.
Requires upstream support from pyVmomi > 6.7.1
(https://github.com/vmware/pyvmomi/pull/732)
Args:
- obj (object): vim object
- properties (list, optional): list of properties following
the property collector specification, for example:
["config.hardware.memoryMB", "name", "overallStatus"]
default is a complete object dump, which can be large
Return:
dict
"""
if not HAS_PYVMOMIJSON:
self.module.fail_json(msg='The installed version of pyvmomi lacks JSON output support; need pyvmomi>6.7.1')
result = dict()
if properties:
for prop in properties:
try:
if '.' in prop:
key, remainder = prop.split('.', 1)
tmp = dict()
tmp[key] = self._extract(self._jsonify(getattr(obj, key)), remainder)
self._deepmerge(result, tmp)
else:
result[prop] = self._jsonify(getattr(obj, prop))
except (AttributeError, KeyError):
self.module.fail_json(msg="Property '{0}' not found.".format(prop))
else:
result = self._jsonify(obj)
return result

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This module is also sponsored by E.T.A.I. (www.etai.fr) # This module is also sponsored by E.T.A.I. (www.etai.fr)
# Copyright (C) 2018 James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
@ -25,7 +26,7 @@ version_added: 2.3
author: author:
- Loic Blot (@nerzhul) <loic.blot@unix-experience.fr> - Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
notes: notes:
- Tested on vSphere 5.5 - Tested on vSphere 5.5, 6.7
requirements: requirements:
- "python >= 2.6" - "python >= 2.6"
- PyVmomi - PyVmomi
@ -79,6 +80,32 @@ options:
default: 'no' default: 'no'
type: bool type: bool
version_added: '2.8' version_added: '2.8'
schema:
description:
- Specify the output schema desired.
- The 'summary' output schema is the legacy output from the module
- The 'vsphere' output schema is the vSphere API class definition
which requires pyvmomi>6.7.1
choices: ['summary', 'vsphere']
default: 'summary'
type: str
version_added: '2.8'
properties:
description:
- Specify the properties to retrieve.
- If not specified, all properties are retrieved (deeply).
- Results are returned in a structure identical to the vsphere API.
- 'Example:'
- ' properties: ['
- ' "config.hardware.memoryMB",'
- ' "config.hardware.numCPU",'
- ' "guest.disk",'
- ' "overallStatus"'
- ' ]'
- Only valid when C(schema) is C(vsphere).
type: list
required: False
version_added: '2.8'
extends_documentation_fragment: vmware.documentation extends_documentation_fragment: vmware.documentation
''' '''
@ -93,6 +120,19 @@ EXAMPLES = '''
uuid: 421e4592-c069-924d-ce20-7e7533fab926 uuid: 421e4592-c069-924d-ce20-7e7533fab926
delegate_to: localhost delegate_to: localhost
register: facts register: facts
- name: Gather some facts from a guest using the vSphere API output schema
vmware_guest_facts:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: no
datacenter: "{{ datacenter_name }}"
name: "{{ vm_name }}"
schema: "vsphere"
properties: ["config.hardware.memoryMB", "guest.disk", "overallStatus"]
delegate_to: localhost
register: facts
''' '''
RETURN = """ RETURN = """
@ -168,11 +208,6 @@ except ImportError:
HAS_VCLOUD = False HAS_VCLOUD = False
class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)
class VmwareTag(VmwareRestClient): class VmwareTag(VmwareRestClient):
def __init__(self, module): def __init__(self, module):
super(VmwareTag, self).__init__(module) super(VmwareTag, self).__init__(module)
@ -189,7 +224,9 @@ def main():
use_instance_uuid=dict(type='bool', default=False), use_instance_uuid=dict(type='bool', default=False),
folder=dict(type='str'), folder=dict(type='str'),
datacenter=dict(type='str', required=True), datacenter=dict(type='str', required=True),
tags=dict(type='bool', default=False) tags=dict(type='bool', default=False),
schema=dict(type='str', choices=['summary', 'vsphere'], default='summary'),
properties=dict(type='list')
) )
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
required_one_of=[['name', 'uuid']]) required_one_of=[['name', 'uuid']])
@ -199,14 +236,21 @@ def main():
# so we should leave the input folder path unmodified # so we should leave the input folder path unmodified
module.params['folder'] = module.params['folder'].rstrip('/') module.params['folder'] = module.params['folder'].rstrip('/')
pyv = PyVmomiHelper(module) if module.params['schema'] != 'vsphere' and module.params.get('properties'):
module.fail_json(msg="The option 'properties' is only valid when the schema is 'vsphere'")
pyv = PyVmomi(module)
# Check if the VM exists before continuing # Check if the VM exists before continuing
vm = pyv.get_vm() vm = pyv.get_vm()
# VM already exists # VM already exists
if vm: if vm:
try: try:
instance = pyv.gather_facts(vm) if module.params['schema'] == 'summary':
instance = pyv.gather_facts(vm)
else:
instance = pyv.to_json(vm, module.params['properties'])
if module.params.get('tags'): if module.params.get('tags'):
if not HAS_VCLOUD: if not HAS_VCLOUD:
module.fail_json(msg="Unable to find 'vCloud Suite SDK' Python library which is required." module.fail_json(msg="Unable to find 'vCloud Suite SDK' Python library which is required."

@ -1,7 +1,8 @@
# Test code for the vmware_guest_facts module. # Test code for the vmware_guest_facts module.
# Copyright: (c) 2017, Abhijeet Kasurde <akasurde@redhat.com> # Copyright: (c) 2017, Abhijeet Kasurde <akasurde@redhat.com>
# Copyright: (c) 2018, James E. King III (@jeking3) <jking@apache.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
---
- name: store the vcenter container ip - name: store the vcenter container ip
set_fact: set_fact:
vcsim: "{{ lookup('env', 'vcenter_host') }}" vcsim: "{{ lookup('env', 'vcenter_host') }}"
@ -62,7 +63,7 @@
folder: "{{ vm1 | dirname }}" folder: "{{ vm1 | dirname }}"
register: guest_facts_0001 register: guest_facts_0001
- debug: msg="{{ guest_facts_0001 }}" - debug: var=guest_facts_0001
- assert: - assert:
that: that:
@ -98,7 +99,25 @@
uuid: "{{ vm1_uuid }}" uuid: "{{ vm1_uuid }}"
register: guest_facts_0002 register: guest_facts_0002
- debug: msg="{{ guest_facts_0002 }}" - debug: var=guest_facts_0002
- name: "Get specific details about virtual machines using the vsphere output schema"
vmware_guest_facts:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
datacenter: "{{ dc1 | basename }}"
uuid: "{{ vm1_uuid }}"
schema: vsphere
properties:
- config.hardware.memoryMB
- guest
- name
- summary.runtime.connectionState
register: guest_facts_0002b
- debug: var=guest_facts_0002b
- assert: - assert:
that: that:
@ -106,14 +125,10 @@
- "guest_facts_0002['instance']['hw_product_uuid'] is defined" - "guest_facts_0002['instance']['hw_product_uuid'] is defined"
- "guest_facts_0002['instance']['hw_product_uuid'] == vm1_uuid" - "guest_facts_0002['instance']['hw_product_uuid'] == vm1_uuid"
- "guest_facts_0002['instance']['hw_cores_per_socket'] is defined" - "guest_facts_0002['instance']['hw_cores_per_socket'] is defined"
- "guest_facts_0001['instance']['hw_datastores'] is defined" - "guest_facts_0002b['instance']['config']['hardware']['memoryMB'] is defined"
- "guest_facts_0001['instance']['hw_esxi_host'] == h1 | basename" - "guest_facts_0002b['instance']['config']['hardware']['numCoresPerSocket'] is not defined"
- "guest_facts_0001['instance']['hw_files'] is defined" - "guest_facts_0002b['instance']['guest']['toolsVersion'] is defined"
- "guest_facts_0001['instance']['hw_guest_ha_state'] is defined" - "guest_facts_0002b['instance']['overallStatus'] is not defined"
- "guest_facts_0001['instance']['hw_is_template'] is defined"
- "guest_facts_0001['instance']['hw_folder'] is defined"
- "guest_facts_0001['instance']['guest_question'] is defined"
- "guest_facts_0001['instance']['guest_consolidation_needed'] is defined"
# Testcase 0003: Get details about virtual machines without snapshots using UUID # Testcase 0003: Get details about virtual machines without snapshots using UUID
- name: get empty list of snapshots from virtual machine using UUID - name: get empty list of snapshots from virtual machine using UUID
@ -126,7 +141,7 @@
uuid: "{{ vm1_uuid }}" uuid: "{{ vm1_uuid }}"
register: guest_facts_0003 register: guest_facts_0003
- debug: msg="{{ guest_facts_0003 }}" - debug: var=guest_facts_0003
- assert: - assert:
that: that:
@ -169,7 +184,7 @@
# uuid: "{{ vm1_uuid }}" # uuid: "{{ vm1_uuid }}"
# register: guest_facts_0004 # register: guest_facts_0004
#- debug: msg="{{ guest_facts_0004 }}" #- debug: var=guest_facts_0004
#- assert: #- assert:
# that: # that:

Loading…
Cancel
Save