diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py index dc63e66f81f..074b6aa23f6 100644 --- a/lib/ansible/module_utils/vmware.py +++ b/lib/ansible/module_utils/vmware.py @@ -144,6 +144,21 @@ def find_datacenter_by_name(content, datacenter_name): return None +def get_parent_datacenter(obj): + """ Walk the parent tree to find the objects datacenter """ + if isinstance(obj, vim.Datacenter): + return obj + datacenter = None + while True: + if not hasattr(obj, 'parent'): + break + obj = obj.parent + if isinstance(obj, vim.Datacenter): + datacenter = obj + break + return datacenter + + def find_datastore_by_name(content, datastore_name): datastores = get_all_objs(content, [vim.Datastore]) diff --git a/lib/ansible/modules/cloud/vmware/vmware_datastore_facts.py b/lib/ansible/modules/cloud/vmware/vmware_datastore_facts.py new file mode 100755 index 00000000000..fd0ec6b2ba4 --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vmware_datastore_facts.py @@ -0,0 +1,188 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Tim Rightnour +# 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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: vmware_datastore_facts +short_description: Gather facts about datastores +description: + - Gather facts about datastores in VMWare +version_added: 2.5 +author: + - Tim Rightnour (@garbled1) +notes: + - Tested on vSphere 5.5 +requirements: + - "python >= 2.6" + - PyVmomi +options: + name: + description: + - Name of a datastore to match + datacenter: + description: + - Datacenter to search for datastores + - This is required if cluster is not supplied + cluster: + description: + - Cluster to search for datastores + - This is required if datacenter is not supplied + required: False +extends_documentation_fragment: vmware.documentation +''' + +EXAMPLES = ''' +- name: Gather facts from standalone ESXi server having datacenter as 'ha-datacenter' + vmware_datastore_facts: + hostname: 192.168.1.209 + username: administrator@vsphere.local + password: vmware + datacenter: ha-datacenter + validate_certs: no + delegate_to: localhost + register: facts +''' + +RETURN = """ +instance: + description: metadata about the available datastores + returned: always + type: dict + sample: None +""" + +try: + import pyVmomi + from pyVmomi import vim +except ImportError: + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.vmware import (connect_to_api, vmware_argument_spec, + get_all_objs, HAS_PYVMOMI, find_obj, + find_cluster_by_name, get_parent_datacenter) + + +class PyVmomiCache(object): + """ This class caches references to objects which are requested multiples times but not modified """ + def __init__(self, content, dc_name=None): + self.content = content + self.dc_name = dc_name + self.clusters = {} + self.parent_datacenters = {} + + def get_all_objs(self, content, types, confine_to_datacenter=True): + """ Wrapper around get_all_objs to set datacenter context """ + objects = get_all_objs(content, types) + if confine_to_datacenter: + if hasattr(objects, 'items'): + # resource pools come back as a dictionary + for k, v in objects.items(): + parent_dc = get_parent_datacenter(k) + if parent_dc.name != self.dc_name: + objects.pop(k, None) + else: + # everything else should be a list + objects = [x for x in objects if get_parent_datacenter(x).name == self.dc_name] + + return objects + + +class PyVmomiHelper(object): + def __init__(self, module): + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi module required') + + self.module = module + self.params = module.params + self.content = connect_to_api(self.module) + self.cache = PyVmomiCache(self.content, dc_name=self.params['datacenter']) + + def lookup_datastore(self): + datastores = self.cache.get_all_objs(self.content, [vim.Datastore], confine_to_datacenter=True) + return datastores + + def lookup_datastore_by_cluster(self): + cluster = find_cluster_by_name(self.content, self.params['cluster']) + if not cluster: + self.module.fail_json(msg='Failed to find cluster "%(cluster)s"' % self.params) + c_dc = cluster.datastore + return c_dc + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update( + name=dict(type='str'), + datacenter=dict(type='str'), + cluster=dict(type='str') + ) + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=[ + ['cluster', 'datacenter'], + ], + ) + result = dict(changed=False) + + pyv = PyVmomiHelper(module) + + if module.params['cluster']: + dxs = pyv.lookup_datastore_by_cluster() + else: + dxs = pyv.lookup_datastore() + + datastores = list() + for ds in dxs: + summary = ds.summary + dds = dict() + dds['accessible'] = summary.accessible + dds['capacity'] = summary.capacity + dds['name'] = summary.name + dds['freeSpace'] = summary.freeSpace + dds['maintenanceMode'] = summary.maintenanceMode + dds['multipleHostAccess'] = summary.multipleHostAccess + dds['type'] = summary.type + # vcsim does not return uncommitted + if not summary.uncommitted: + summary.uncommitted = 0 + dds['uncommitted'] = summary.uncommitted + dds['url'] = summary.url + # Calculated values + dds['provisioned'] = summary.capacity - summary.freeSpace + summary.uncommitted + if module.params['name']: + if dds['name'] == module.params['name']: + datastores.extend([dds]) + else: + datastores.extend([dds]) + + result['datastores'] = datastores + + # found a datastore + if datastores: + try: + module.exit_json(**result) + except Exception as exc: + module.fail_json(msg="Fact gather failed with exception %s" % to_text(exc)) + else: + msg = "Unable to gather datastore facts" + if module.params['name']: + msg += " for %(name)s" % module.params + msg += " in datacenter %(datacenter)s" % module.params + module.fail_json(msg=msg) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/vmware_datastore_facts/aliases b/test/integration/targets/vmware_datastore_facts/aliases new file mode 100644 index 00000000000..4c6228bf7a7 --- /dev/null +++ b/test/integration/targets/vmware_datastore_facts/aliases @@ -0,0 +1,3 @@ +posix/ci/cloud/group1/vcenter +cloud/vcenter +destructive diff --git a/test/integration/targets/vmware_datastore_facts/tasks/main.yml b/test/integration/targets/vmware_datastore_facts/tasks/main.yml new file mode 100644 index 00000000000..6f67563ddba --- /dev/null +++ b/test/integration/targets/vmware_datastore_facts/tasks/main.yml @@ -0,0 +1,129 @@ +# Test code for the vmware_datastore_facts module. +# (c) 2017, Tim Rightnour + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +- name: make sure pyvmomi is installed + pip: + name: pyvmomi + state: latest + +- name: store the vcenter container ip + set_fact: + vcsim: "{{ lookup('env', 'vcenter_host') }}" + +- debug: + var: vcsim + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/killall' }}" + +- name: start vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/spawn?ds=2&datacenter=1&cluster=1&folder=0' }}" + register: vcsim_instance + +- name: Wait for vcsim server to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get a list of Clusters from vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=CCR' }}" + register: clusters + +- set_fact: + cl1: "{{ clusters['json'][0] }}" + +- name: get a list of Datacenters from vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=DC' }}" + register: datacenters + +- set_fact: + dc1: "{{ datacenters['json'][0] }}" + +- name: get a list of Datastores from vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=D' }}" + register: datastores + +- set_fact: + ds1: "{{ datastores['json'][0] }}" + +# Testcase 0001: Get a full list of datastores in a datacenter +- name: get list of facts about datastores + vmware_datastore_facts: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + datacenter: "{{ dc1 | basename }}" + register: datastore_facts_0001 + +- debug: + msg: "{{ datastore_facts_0001 }}" + +- assert: + that: + - "datastore_facts_0001['datastores'][0]['capacity'] is defined" + - "datastore_facts_0001['datastores'][1]['capacity'] is defined" + +# Testcase 0002: Get a full list of datastores in a cluster +- name: get list of facts about datastores - no dc + vmware_datastore_facts: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + cluster: "{{ cl1 | basename }}" + register: datastore_facts_0002 + +- debug: + msg: "{{ datastore_facts_0002 }}" + +- assert: + that: + - "datastore_facts_0002['datastores'][0]['capacity'] is defined" + - "datastore_facts_0002['datastores'][1]['capacity'] is defined" + +# Testcase 0003: Find a specific datastore +- name: get list of facts about one datastore + vmware_datastore_facts: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + datacenter: "{{ dc1 | basename }}" + name: "{{ ds1 | basename }}" + register: datastore_facts_0003 + +- debug: + msg: "{{ datastore_facts_0003 }}" + +- assert: + that: + - "datastore_facts_0003['datastores'][0]['name'] == ds1 | basename" + - "datastore_facts_0003['datastores'][0]['capacity'] is defined"