Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VMware: vmware_guest_facts: add the ability to get guest facts and specific properties using the vSphere schema #47446

Merged
merged 1 commit into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 107 additions & 1 deletion lib/ansible/module_utils/vmware.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# 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)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

import atexit
import ansible.module_utils.common._collections_compat as collections_compat
import json
import os
import re
import ssl
Expand All @@ -26,11 +29,13 @@
PYVMOMI_IMP_ERR = None
try:
from pyVim import connect
from pyVmomi import vim, vmodl
from pyVmomi import vim, vmodl, VmomiSupport
HAS_PYVMOMI = True
HAS_PYVMOMIJSON = hasattr(VmomiSupport, 'VmomiJSONEncoder')
except ImportError:
PYVMOMI_IMP_ERR = traceback.format_exc()
HAS_PYVMOMI = False
HAS_PYVMOMIJSON = False

from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.six import integer_types, iteritems, string_types, raise_from
Expand Down Expand Up @@ -1245,3 +1250,104 @@ def find_vmdk_file(self, datastore_obj, vmdk_fullpath, vmdk_filename, vmdk_folde
return f

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
"""
Akasurde marked this conversation as resolved.
Show resolved Hide resolved
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
62 changes: 53 additions & 9 deletions lib/ansible/modules/cloud/vmware/vmware_guest_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# 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)

from __future__ import absolute_import, division, print_function
Expand All @@ -25,7 +26,7 @@
author:
- Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
notes:
- Tested on vSphere 5.5
- Tested on vSphere 5.5, 6.7
requirements:
- "python >= 2.6"
- PyVmomi
Expand Down Expand Up @@ -73,6 +74,32 @@
default: 'no'
type: bool
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
'''

Expand All @@ -87,6 +114,19 @@
uuid: 421e4592-c069-924d-ce20-7e7533fab926
delegate_to: localhost
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 = """
Expand Down Expand Up @@ -161,11 +201,6 @@
HAS_VCLOUD = False


class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)


class VmwareTag(VmwareRestClient):
def __init__(self, module):
super(VmwareTag, self).__init__(module)
Expand All @@ -181,7 +216,9 @@ def main():
uuid=dict(type='str'),
folder=dict(type='str'),
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,
required_one_of=[['name', 'uuid']])
Expand All @@ -191,14 +228,21 @@ def main():
# so we should leave the input folder path unmodified
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
vm = pyv.get_vm()

# VM already exists
if vm:
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 not HAS_VCLOUD:
module.fail_json(msg="Unable to find 'vCloud Suite SDK' Python library which is required."
Expand Down
41 changes: 28 additions & 13 deletions test/integration/targets/vmware_guest_facts/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Test code for the vmware_guest_facts module.
# 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)

---
- name: store the vcenter container ip
set_fact:
vcsim: "{{ lookup('env', 'vcenter_host') }}"
Expand Down Expand Up @@ -62,7 +63,7 @@
folder: "{{ vm1 | dirname }}"
register: guest_facts_0001

- debug: msg="{{ guest_facts_0001 }}"
- debug: var=guest_facts_0001

- assert:
that:
Expand Down Expand Up @@ -95,22 +96,36 @@
uuid: "{{ vm1_uuid }}"
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:
that:
- "guest_facts_0002['instance']['hw_name'] == vm1 | basename"
- "guest_facts_0002['instance']['hw_product_uuid'] is defined"
- "guest_facts_0002['instance']['hw_product_uuid'] == vm1_uuid"
- "guest_facts_0002['instance']['hw_cores_per_socket'] is defined"
- "guest_facts_0001['instance']['hw_datastores'] is defined"
- "guest_facts_0001['instance']['hw_esxi_host'] == h1 | basename"
- "guest_facts_0001['instance']['hw_files'] is defined"
- "guest_facts_0001['instance']['hw_guest_ha_state'] is 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"
- "guest_facts_0002b['instance']['config']['hardware']['memoryMB'] is defined"
- "guest_facts_0002b['instance']['config']['hardware']['numCoresPerSocket'] is not defined"
- "guest_facts_0002b['instance']['guest']['toolsVersion'] is defined"
- "guest_facts_0002b['instance']['overallStatus'] is not defined"

# Testcase 0003: Get details about virtual machines without snapshots using UUID
- name: get empty list of snapshots from virtual machine using UUID
Expand All @@ -123,7 +138,7 @@
uuid: "{{ vm1_uuid }}"
register: guest_facts_0003

- debug: msg="{{ guest_facts_0003 }}"
- debug: var=guest_facts_0003

- assert:
that:
Expand Down Expand Up @@ -166,7 +181,7 @@
# uuid: "{{ vm1_uuid }}"
# register: guest_facts_0004

#- debug: msg="{{ guest_facts_0004 }}"
#- debug: var=guest_facts_0004

#- assert:
# that:
Expand Down