diff --git a/CHANGELOG.md b/CHANGELOG.md index 539a77a500e..6f5a8dfdf22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,9 @@ Ansible Changes By Release * win_say - openstack * os_quota +- zfs: + * zfs_facts + * zpool_facts ####New Callbacks: diff --git a/lib/ansible/modules/storage/zfs/__init__.py b/lib/ansible/modules/storage/zfs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/system/zfs.py b/lib/ansible/modules/storage/zfs/zfs.py similarity index 100% rename from lib/ansible/modules/system/zfs.py rename to lib/ansible/modules/storage/zfs/zfs.py diff --git a/lib/ansible/modules/storage/zfs/zfs_facts.py b/lib/ansible/modules/storage/zfs/zfs_facts.py new file mode 100644 index 00000000000..162b727beee --- /dev/null +++ b/lib/ansible/modules/storage/zfs/zfs_facts.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Števko +# +# 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 . +# + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: zfs_facts +short_description: Gather facts about ZFS datasets. +description: + - Gather facts from ZFS dataset properties. +version_added: "2.3" +author: Adam Števko (@xen0l) +options: + name: + description: + - ZFS dataset name. + alias: [ "ds", "dataset" ] + type: str + required: yes + recurse: + description: + - Specifies if properties for any children should be recursively + displayed. + type: bool + default: False + required: false + parsable: + description: + - Specifies if property values should be displayed in machine + friendly format. + type: bool + default: False + required: false + properties: + description: + - Specifies which dataset properties should be queried in comma-separated format. + For more information about dataset properties, check zfs(1M) man page. + alias: [ "props" ] + type: str + default: all + required: false + type: + description: + - Specifies which datasets types to display. Multiple values have to be + provided in comma-separated form. + alias: [ "props" ] + type: str + default: all + choices: [ 'all', 'filesystem', 'volume', 'snapshot', 'bookmark' ] + required: false + depth: + description: + - Specifiies recurion depth. + type: int + default: None + required: false +''' + +EXAMPLES = ''' +name: Gather facts about ZFS dataset rpool/export/home +zfs_facts: dataset=rpool/export/home + +name: Report space usage on ZFS filesystems under data/home +zfs_facts: name=data/home recurse=yes type=filesystem +debug: msg='ZFS dataset {{ item.name }} consumes {{ item.used }} of disk space.' +with_items: '{{ ansible_zfs_datasets }} +''' + +RETURN = ''' +name: + description: ZFS dataset name + returned: always + type: string + sample: rpool/var/spool +parsable: + description: if parsable output should be provided in machine friendly format. + returned: if 'parsable' is set to True + type: boolean + sample: True +recurse: + description: if we should recurse over ZFS dataset + returned: if 'recurse' is set to True + type: boolean + sample: True +zfs_datasets: + description: ZFS dataset facts + returned: always + type: string + sample: + { + "aclinherit": "restricted", + "aclmode": "discard", + "atime": "on", + "available": "43.8G", + "canmount": "on", + "casesensitivity": "sensitive", + "checksum": "on", + "compression": "off", + "compressratio": "1.00x", + "copies": "1", + "creation": "Thu Jun 16 11:37 2016", + "dedup": "off", + "devices": "on", + "exec": "on", + "filesystem_count": "none", + "filesystem_limit": "none", + "logbias": "latency", + "logicalreferenced": "18.5K", + "logicalused": "3.45G", + "mlslabel": "none", + "mounted": "yes", + "mountpoint": "/rpool", + "name": "rpool", + "nbmand": "off", + "normalization": "none", + "org.openindiana.caiman:install": "ready", + "primarycache": "all", + "quota": "none", + "readonly": "off", + "recordsize": "128K", + "redundant_metadata": "all", + "refcompressratio": "1.00x", + "referenced": "29.5K", + "refquota": "none", + "refreservation": "none", + "reservation": "none", + "secondarycache": "all", + "setuid": "on", + "sharenfs": "off", + "sharesmb": "off", + "snapdir": "hidden", + "snapshot_count": "none", + "snapshot_limit": "none", + "sync": "standard", + "type": "filesystem", + "used": "4.41G", + "usedbychildren": "4.41G", + "usedbydataset": "29.5K", + "usedbyrefreservation": "0", + "usedbysnapshots": "0", + "utf8only": "off", + "version": "5", + "vscan": "off", + "written": "29.5K", + "xattr": "on", + "zoned": "off" + } +''' + +import os +from collections import defaultdict +from ansible.module_utils.six import iteritems +from ansible.module_utils.basic import AnsibleModule + +SUPPORTED_TYPES = ['all', 'filesystem', 'volume', 'snapshot', 'bookmark'] + + +class ZFSFacts(object): + def __init__(self, module): + + self.module = module + + self.name = module.params['name'] + self.recurse = module.params['recurse'] + self.parsable = module.params['parsable'] + self.properties = module.params['properties'] + self.type = module.params['type'] + self.depth = module.params['depth'] + + self._datasets = defaultdict(dict) + self.facts = [] + + def dataset_exists(self): + cmd = [self.module.get_bin_path('zfs')] + + cmd.append('list') + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def get_facts(self): + cmd = [self.module.get_bin_path('zfs')] + + cmd.append('get') + cmd.append('-H') + if self.parsable: + cmd.append('-p') + if self.recurse: + cmd.append('-r') + if int(self.depth) != 0: + cmd.append('-d') + cmd.append('%s' % self.depth) + if self.type: + cmd.append('-t') + cmd.append(self.type) + cmd.append('-o') + cmd.append('name,property,value') + cmd.append(self.properties) + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + for line in out.splitlines(): + dataset, property, value = line.split('\t') + + self._datasets[dataset].update({property: value}) + + for k, v in iteritems(self._datasets): + v.update({'name': k}) + self.facts.append(v) + + return {'ansible_zfs_datasets': self.facts} + else: + self.module.fail_json(msg='Error while trying to get facts about ZFS dataset: %s' % self.name, + stderr=err, + rc=rc) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=['ds', 'dataset'], type='str'), + recurse=dict(required=False, default=False, type='bool'), + parsable=dict(required=False, default=False, type='bool'), + properties=dict(required=False, default='all', type='str'), + type=dict(required=False, default='all', type='str', choices=SUPPORTED_TYPES), + depth=dict(required=False, default=0, type='int') + ), + supports_check_mode=True + ) + + zfs_facts = ZFSFacts(module) + + result = {} + result['changed'] = False + result['name'] = zfs_facts.name + + if zfs_facts.parsable: + result['parsable'] = zfs_facts.parsable + + if zfs_facts.recurse: + result['recurse'] = zfs_facts.recurse + + if zfs_facts.dataset_exists(): + result['ansible_facts'] = zfs_facts.get_facts() + else: + module.fail_json(msg='ZFS dataset %s does not exist!' % zfs_facts.name) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/zfs/zpool_facts.py b/lib/ansible/modules/storage/zfs/zpool_facts.py new file mode 100644 index 00000000000..ba9dbccfe7d --- /dev/null +++ b/lib/ansible/modules/storage/zfs/zpool_facts.py @@ -0,0 +1,220 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Števko +# +# 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 . +# + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: zpool_facts +short_description: Gather facts about ZFS pools. +description: + - Gather facts from ZFS pool properties. +version_added: "2.3" +author: Adam Števko (@xen0l) +options: + name: + description: + - ZFS pool name. + alias: [ "pool", "zpool" ] + type: str + required: false + parsable: + description: + - Specifies if property values should be displayed in machine + friendly format. + type: bool + default: False + required: false + properties: + description: + - Specifies which dataset properties should be queried in comma-separated format. + For more information about dataset properties, check zpool(1M) man page. + alias: [ "props" ] + type: str + default: all + required: false +''' + +EXAMPLES = ''' +# Gather facts about ZFS pool rpool +zpool_facts: pool=rpool + +# Gather space usage about all imported ZFS pools +zpool_facts: properties='free,size' +debug: msg='ZFS pool {{ item.name }} has {{ item.free }} free space out of {{ item.size }}.' +with_items: '{{ ansible_zfs_pools }}' +''' + +RETURN = ''' +name: + description: ZFS pool name + returned: always + type: string + sample: rpool +parsable: + description: if parsable output should be provided in machine friendly format. + returned: if 'parsable' is set to True + type: boolean + sample: True +zfs_pools: + description: ZFS pool facts + returned: always + type: string + sample: + { + "allocated": "3.46G", + "altroot": "-", + "autoexpand": "off", + "autoreplace": "off", + "bootfs": "rpool/ROOT/openindiana", + "cachefile": "-", + "capacity": "6%", + "comment": "-", + "dedupditto": "0", + "dedupratio": "1.00x", + "delegation": "on", + "expandsize": "-", + "failmode": "wait", + "feature@async_destroy": "enabled", + "feature@bookmarks": "enabled", + "feature@edonr": "enabled", + "feature@embedded_data": "active", + "feature@empty_bpobj": "active", + "feature@enabled_txg": "active", + "feature@extensible_dataset": "enabled", + "feature@filesystem_limits": "enabled", + "feature@hole_birth": "active", + "feature@large_blocks": "enabled", + "feature@lz4_compress": "active", + "feature@multi_vdev_crash_dump": "enabled", + "feature@sha512": "enabled", + "feature@skein": "enabled", + "feature@spacemap_histogram": "active", + "fragmentation": "3%", + "free": "46.3G", + "freeing": "0", + "guid": "15729052870819522408", + "health": "ONLINE", + "leaked": "0", + "listsnapshots": "off", + "name": "rpool", + "readonly": "off", + "size": "49.8G", + "version": "-" + } +''' + +import os +from collections import defaultdict +from ansible.module_utils.six import iteritems +from ansible.module_utils.basic import AnsibleModule + +class ZPoolFacts(object): + def __init__(self, module): + + self.module = module + + self.name = module.params['name'] + self.parsable = module.params['parsable'] + self.properties = module.params['properties'] + + self._pools = defaultdict(dict) + self.facts = [] + + def pool_exists(self): + cmd = [self.module.get_bin_path('zpool')] + + cmd.append('list') + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def get_facts(self): + cmd = [self.module.get_bin_path('zpool')] + + cmd.append('get') + cmd.append('-H') + if self.parsable: + cmd.append('-p') + cmd.append('-o') + cmd.append('name,property,value') + cmd.append(self.properties) + if self.name: + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + + if rc == 0: + for line in out.splitlines(): + pool, property, value = line.split('\t') + + self._pools[pool].update({property: value}) + + for k, v in iteritems(self._pools): + v.update({'name': k}) + self.facts.append(v) + + return {'ansible_zfs_pools': self.facts} + else: + self.module.fail_json(msg='Error while trying to get facts about ZFS pool: %s' % self.name, + stderr=err, + rc=rc) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=False, aliases=['pool', 'zpool'], type='str'), + parsable=dict(required=False, default=False, type='bool'), + properties=dict(required=False, default='all', type='str'), + ), + supports_check_mode=True + ) + + zpool_facts = ZPoolFacts(module) + + result = {} + result['changed'] = False + result['name'] = zpool_facts.name + + if zpool_facts.parsable: + result['parsable'] = zpool_facts.parsable + + if zpool_facts.name is not None: + if zpool_facts.pool_exists(): + result['ansible_facts'] = zpool_facts.get_facts() + else: + module.fail_json(msg='ZFS pool %s does not exist!' % zpool_facts.name) + else: + result['ansible_facts'] = zpool_facts.get_facts() + + module.exit_json(**result) + + +if __name__ == '__main__': + main()