diff --git a/CHANGELOG.md b/CHANGELOG.md index 823cb9419a..aaf276987e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,13 @@ Ansible Changes By Release * ipa_sudocmd * ipa_sudorule * ipa_user +- infinibox: + * infini_export + * infini_export_client + * infini_fs + * infini_host + * infini_pool + * infini_vol - openwrt_init - windows: * win_say diff --git a/lib/ansible/module_utils/infinibox.py b/lib/ansible/module_utils/infinibox.py new file mode 100644 index 0000000000..c16037e9a0 --- /dev/null +++ b/lib/ansible/module_utils/infinibox.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Gregory Shulov ,2016 +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from functools import wraps +from os import environ +from os import path + + +def api_wrapper(func): + """ Catch API Errors Decorator""" + @wraps(func) + def __wrapper(*args, **kwargs): + module = args[0] + try: + return func(*args, **kwargs) + except core.exceptions.APICommandException as e: + module.fail_json(msg=e.message) + except core.exceptions.SystemNotFoundException as e: + module.fail_json(msg=e.message) + except: + raise + return __wrapper + + +@api_wrapper +def get_system(module): + """Return System Object or Fail""" + box = module.params['system'] + user = module.params.get('user', None) + password = module.params.get('password', None) + + if user and password: + system = InfiniBox(box, auth=(user, password)) + elif environ.get('INFINIBOX_USER') and environ.get('INFINIBOX_PASSWORD'): + system = InfiniBox(box, auth=(environ.get('INFINIBOX_USER'), environ.get('INFINIBOX_PASSWORD'))) + elif path.isfile(path.expanduser('~') + '/.infinidat/infinisdk.ini'): + system = InfiniBox(box) + else: + module.fail_json(msg="You must set INFINIBOX_USER and INFINIBOX_PASSWORD environment variables or set username/password module arguments") + + try: + system.login() + except Exception: + module.fail_json(msg="Infinibox authentication failed. Check your credentials") + return system + + +def infinibox_argument_spec(): + """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" + + return dict( + system = dict(required=True), + user = dict(), + password = dict(no_log=True), + ) + + +def infinibox_required_together(): + """Return the default list used for the required_together argument to AnsibleModule""" + return [['user', 'password']] diff --git a/lib/ansible/modules/storage/infinidat/__init__.py b/lib/ansible/modules/storage/infinidat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/modules/storage/infinidat/infini_export.py b/lib/ansible/modules/storage/infinidat/infini_export.py new file mode 100644 index 0000000000..683234426a --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_export.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_export +version_added: 2.3 +short_description: Create, Delete or Modify NFS Exports on Infinibox +description: + - This module creates, deletes or modifies NFS exports on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + name: + description: + - Export name. Should always start with C(/). (ex. name=/data) + aliases: ['export', 'path'] + required: true + state: + description: + - Creates/Modifies export when present and removes when absent. + required: false + default: "present" + choices: [ "present", "absent" ] + inner_path: + description: + - Internal path of the export. + default: "/" + client_list: + description: + - List of dictionaries with client entries. See examples. + Check infini_export_client module to modify individual NFS client entries for export. + default: "All Hosts(*), RW, no_root_squash: True" + required: false + filesystem: + description: + - Name of exported file system. + required: true +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Export bar filesystem under foo pool as /data + infini_export: + name: /data01 + filesystem: foo + user: admin + password: secret + system: ibox001 + +- name: Export and specify client list explicitly + infini_export: + name: /data02 + filesystem: foo + client_list: + - client: 192.168.0.2 + access: RW + no_root_squash: True + - client: 192.168.0.100 + access: RO + no_root_squash: False + - client: 192.168.0.10-192.168.0.20 + access: RO + no_root_squash: False + system: ibox001 + user: admin + password: secret +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from munch import unmunchify + + +def transform(d): + return frozenset(d.items()) + + +@api_wrapper +def get_filesystem(module, system): + """Return Filesystem or None""" + try: + return system.filesystems.get(name=module.params['filesystem']) + except: + return None + + +@api_wrapper +def get_export(module, filesystem, system): + """Retrun export if found. When not found return None""" + + export = None + exports_to_list = system.exports.to_list() + + for e in exports_to_list: + if e.get_export_path() == module.params['name']: + export = e + break + + return export + + +@api_wrapper +def update_export(module, export, filesystem, system): + """ Create new filesystem or update existing one""" + + changed = False + + name = module.params['name'] + client_list = module.params['client_list'] + + if export is None: + if not module.check_mode: + export = system.exports.create(export_path=name, filesystem=filesystem) + if client_list: + export.update_permissions(client_list) + changed = True + else: + if client_list: + if set(map(transform, unmunchify(export.get_permissions()))) != set(map(transform, client_list)): + if not module.check_mode: + export.update_permissions(client_list) + changed = True + + module.exit_json(changed=changed) + + +@api_wrapper +def delete_export(module, export): + """ Delete file system""" + if not module.check_mode: + export.delete() + module.exit_json(changed=True) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + name = dict(required=True), + state = dict(default='present', choices=['present', 'absent']), + filesystem = dict(required=True), + client_list = dict(type='list') + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + state = module.params['state'] + system = get_system(module) + filesystem = get_filesystem(module, system) + export = get_export(module, filesystem, system) + + if filesystem is None: + module.fail_json(msg='Filesystem {} not found'.format(module.params['filesystem'])) + + if state == 'present': + update_export(module, export, filesystem, system) + elif export and state == 'absent': + delete_export(module, export) + elif export is None and state == 'absent': + module.exit_json(changed=False) + + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/infinidat/infini_export_client.py b/lib/ansible/modules/storage/infinidat/infini_export_client.py new file mode 100644 index 0000000000..1dcde6305d --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_export_client.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_export_client +version_added: 2.3 +short_description: Create, Delete or Modify NFS Client(s) for existing exports on Infinibox +description: + - This module creates, deletes or modifys NFS client(s) for existing exports on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + client: + description: + - Client IP or Range. Ranges can be defined as follows + 192.168.0.1-192.168.0.254. + aliases: ['name'] + required: true + state: + description: + - Creates/Modifies client when present and removes when absent. + required: false + default: "present" + choices: [ "present", "absent" ] + access_mode: + description: + - Read Write or Read Only Access. + choices: [ "RW", "RO" ] + default: RW + required: false + no_root_squash: + description: + - Don't squash root user to anonymous. Will be set to "no" on creation if not specified explicitly. + choices: [ "yes", "no" ] + default: no + required: false + export: + description: + - Name of the export. + required: true +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Make sure nfs client 10.0.0.1 is configured for export. Allow root access + infini_export_client: + client: 10.0.0.1 + access_mode: RW + no_root_squash: yes + export: /data + user: admin + password: secret + system: ibox001 + +- name: Add multiple clients with RO access. Squash root priviledges + infini_export_client: + client: "{{ item }}" + access_mode: RO + no_root_squash: no + export: /data + user: admin + password: secret + system: ibox001 + with_items: + - 10.0.0.2 + - 10.0.0.3 +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from munch import Munch, unmunchify + + +def transform(d): + return frozenset(d.items()) + + +@api_wrapper +def get_export(module, system): + """Retrun export if found. Fail module if not found""" + + try: + export = system.exports.get(export_path=module.params['export']) + except: + module.fail_json(msg="Export with export path {} not found".format(module.params['export'])) + + return export + + +@api_wrapper +def update_client(module, export): + """Update export client list""" + + changed = False + + client = module.params['client'] + access_mode = module.params['access_mode'] + no_root_squash = module.params['no_root_squash'] + + client_list = export.get_permissions() + client_not_in_list = True + + for index, item in enumerate(client_list): + if item.client == client: + client_not_in_list = False + if item.access != access_mode: + item.access = access_mode + changed = True + if item.no_root_squash is not no_root_squash: + item.no_root_squash = no_root_squash + changed = True + + # If access_mode and/or no_root_squash not passed as arguments to the module, + # use access_mode with RW value and set no_root_squash to False + if client_not_in_list: + changed = True + client_list.append(Munch(client=client, access=access_mode, no_root_squash=no_root_squash)) + + if changed: + for index, item in enumerate(client_list): + client_list[index] = unmunchify(item) + if not module.check_mode: + export.update_permissions(client_list) + + module.exit_json(changed=changed) + + +@api_wrapper +def delete_client(module, export): + """Update export client list""" + + changed = False + + client = module.params['client'] + client_list = export.get_permissions() + + for index, item in enumerate(client_list): + if item.client == client: + changed = True + del client_list[index] + + if changed: + for index, item in enumerate(client_list): + client_list[index] = unmunchify(item) + if not module.check_mode: + export.update_permissions(client_list) + + module.exit_json(changed=changed) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + client = dict(required=True), + access_mode = dict(choices=['RO', 'RW'], default='RW'), + no_root_squash = dict(type='bool', default='no'), + state = dict(default='present', choices=['present', 'absent']), + export = dict(required=True) + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + system = get_system(module) + export = get_export(module, system) + + if module.params['state'] == 'present': + update_client(module, export) + else: + delete_client(module, export) + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/infinidat/infini_fs.py b/lib/ansible/modules/storage/infinidat/infini_fs.py new file mode 100644 index 0000000000..0c43605f9c --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_fs.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_fs +version_added: 2.3 +short_description: Create, Delete or Modify filesystems on Infinibox +description: + - This module creates, deletes or modifies filesystems on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + name: + description: + - File system name. + required: true + state: + description: + - Creates/Modifies file system when present or removes when absent. + required: false + default: present + choices: [ "present", "absent" ] + size: + description: + - File system size in MB, GB or TB units. See examples. + required: false + pool: + description: + - Pool that will host file system. + required: true +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Create new file system named foo under pool named bar + infini_fs: + name: foo + size: 1TB + pool: bar + state: present + user: admin + password: secret + system: ibox001 +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from capacity import KiB, Capacity + + +@api_wrapper +def get_pool(module, system): + """Return Pool or None""" + try: + return system.pools.get(name=module.params['pool']) + except: + return None + + +@api_wrapper +def get_filesystem(module, system): + """Return Filesystem or None""" + try: + return system.filesystems.get(name=module.params['name']) + except: + return None + + +@api_wrapper +def create_filesystem(module, system): + """Create Filesystem""" + if not module.check_mode: + filesystem = system.filesystems.create(name=module.params['name'], pool=get_pool(module, system)) + if module.params['size']: + size = Capacity(module.params['size']).roundup(64 * KiB) + filesystem.update_size(size) + module.exit_json(changed=True) + + +@api_wrapper +def update_filesystem(module, filesystem): + """Update Filesystem""" + changed = False + if module.params['size']: + size = Capacity(module.params['size']).roundup(64 * KiB) + if filesystem.get_size() != size: + if not module.check_mode: + filesystem.update_size(size) + changed = True + + module.exit_json(changed=changed) + + +@api_wrapper +def delete_filesystem(module, filesystem): + """ Delete Filesystem""" + if not module.check_mode: + filesystem.delete() + module.exit_json(changed=True) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + name = dict(required=True), + state = dict(default='present', choices=['present', 'absent']), + pool = dict(required=True), + size = dict() + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + if module.params['size']: + try: + Capacity(module.params['size']) + except: + module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units') + + state = module.params['state'] + system = get_system(module) + pool = get_pool(module, system) + filesystem = get_filesystem(module, system) + + if pool is None: + module.fail_json(msg='Pool {} not found'.format(module.params['pool'])) + + if state == 'present' and not filesystem: + create_filesystem(module, system) + elif state == 'present' and filesystem: + update_filesystem(module, filesystem) + elif state == 'absent' and filesystem: + delete_filesystem(module, filesystem) + elif state == 'absent' and not filesystem: + module.exit_json(changed=False) + + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/infinidat/infini_host.py b/lib/ansible/modules/storage/infinidat/infini_host.py new file mode 100644 index 0000000000..17b93e962f --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_host.py @@ -0,0 +1,178 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_host +version_added: 2.3 +short_description: Create, Delete and Modify Hosts on Infinibox +description: + - This module creates, deletes or modifies hosts on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + name: + description: + - Host Name + required: true + state: + description: + - Creates/Modifies Host when present or removes when absent + required: false + default: present + choices: [ "present", "absent" ] + wwns: + description: + - List of wwns of the host + required: false + volume: + description: + - Volume name to map to the host + required: false +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Create new new host + infini_host: + name: foo.example.com + user: admin + password: secret + system: ibox001 + +- name: Make sure host bar is available with wwn ports + infini_host: + name: bar.example.com + wwns: + - "00:00:00:00:00:00:00" + - "11:11:11:11:11:11:11" + system: ibox01 + user: admin + password: secret + +- name: Map host foo.example.com to volume bar + infini_host: + name: foo.example.com + volume: bar + system: ibox01 + user: admin + password: secret +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from collections import Counter + + +@api_wrapper +def get_host(module, system): + + host = None + + for h in system.hosts.to_list(): + if h.get_name() == module.params['name']: + host = h + break + + return host + + +@api_wrapper +def create_host(module, system): + + changed = True + + if not module.check_mode: + host = system.hosts.create(name=module.params['name']) + if module.params['wwns']: + for p in module.params['wwns']: + host.add_fc_port(p) + if module.params['volume']: + host.map_volume(system.volumes.get(name=module.params['volume'])) + module.exit_json(changed=changed) + + +@api_wrapper +def update_host(module, host): + changed = False + name = module.params['name'] + module.exit_json(changed=changed) + + +@api_wrapper +def delete_host(module, host): + changed = True + if not module.check_mode: + host.delete() + module.exit_json(changed=changed) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + name = dict(required=True), + state = dict(default='present', choices=['present', 'absent']), + wwns = dict(type='list'), + volume = dict() + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + state = module.params['state'] + system = get_system(module) + host = get_host(module, system) + + if module.params['volume']: + try: + system.volumes.get(name=module.params['volume']) + except: + module.fail_json(msg='Volume {} not found'.format(module.params['volume'])) + + if host and state == 'present': + update_host(module, host) + elif host and state == 'absent': + delete_host(module, host) + elif host is None and state == 'absent': + module.exit_json(changed=False) + else: + create_host(module, system) + + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/infinidat/infini_pool.py b/lib/ansible/modules/storage/infinidat/infini_pool.py new file mode 100644 index 0000000000..7fd4839503 --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_pool.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_pool +version_added: 2.3 +short_description: Create, Delete and Modify Pools on Infinibox +description: + - This module to creates, deletes or modifies pools on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + name: + description: + - Pool Name + required: true + state: + description: + - Creates/Modifies Pool when present or removes when absent + required: false + default: present + choices: [ "present", "absent" ] + size: + description: + - Pool Physical Capacity in MB, GB or TB units. + If pool size is not set on pool creation, size will be equal to 1TB. + See examples. + required: false + vsize: + description: + - Pool Virtual Capacity in MB, GB or TB units. + If pool vsize is not set on pool creation, Virtual Capacity will be equal to Physical Capacity. + See examples. + required: false + ssd_cache: + description: + - Enable/Disable SSD Cache on Pool + required: false + default: yes + choices: [ "yes", "no" ] +notes: + - Infinibox Admin level access is required for pool modifications +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Make sure pool foo exists. Set pool physical capacity to 10TB + infini_pool: + name: foo + size: 10TB + vsize: 10TB + user: admin + password: secret + system: ibox001 + +- name: Disable SSD Cache on pool + infini_pool: + name: foo + ssd_cache: no + user: admin + password: secret + system: ibox001 +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from capacity import KiB, Capacity + + +@api_wrapper +def get_pool(module, system): + """Return Pool on None""" + try: + return system.pools.get(name=module.params['name']) + except: + return None + + +@api_wrapper +def create_pool(module, system): + """Create Pool""" + name = module.params['name'] + size = module.params['size'] + vsize = module.params['vsize'] + ssd_cache = module.params['ssd_cache'] + + if not module.check_mode: + if not size and not vsize: + pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity('1TB')) + elif size and not vsize: + pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(size)) + elif not size and vsize: + pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity(vsize)) + else: + pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(vsize)) + # Default value of ssd_cache is True. Disable ssd chacing if False + if not ssd_cache: + pool.update_ssd_enabled(ssd_cache) + + module.exit_json(changed=True) + + +@api_wrapper +def update_pool(module, system, pool): + """Update Pool""" + changed = False + + size = module.params['size'] + vsize = module.params['vsize'] + ssd_cache = module.params['ssd_cache'] + + # Roundup the capacity to mimic Infinibox behaviour + if size: + physical_capacity = Capacity(size).roundup(6 * 64 * KiB) + if pool.get_physical_capacity() != physical_capacity: + if not module.check_mode: + pool.update_physical_capacity(physical_capacity) + changed = True + + if vsize: + virtual_capacity = Capacity(vsize).roundup(6 * 64 * KiB) + if pool.get_virtual_capacity() != virtual_capacity: + if not module.check_mode: + pool.update_virtual_capacity(virtual_capacity) + changed = True + + if pool.get_ssd_enabled() != ssd_cache: + if not module.check_mode: + pool.update_ssd_enabled(ssd_cache) + changed = True + + module.exit_json(changed=changed) + + +@api_wrapper +def delete_pool(module, pool): + """Delete Pool""" + if not module.check_mode: + pool.delete() + module.exit_json(changed=True) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + name = dict(required=True), + state = dict(default='present', choices=['present', 'absent']), + size = dict(), + vsize = dict(), + ssd_cache = dict(type='bool', default=True) + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + if module.params['size']: + try: + Capacity(module.params['size']) + except: + module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units') + + if module.params['vsize']: + try: + Capacity(module.params['vsize']) + except: + module.fail_json(msg='vsize (Virtual Capacity) should be defined in MB, GB, TB or PB units') + + state = module.params['state'] + system = get_system(module) + pool = get_pool(module, system) + + if state == 'present' and not pool: + create_pool(module, system) + elif state == 'present' and pool: + update_pool(module, system, pool) + elif state == 'absent' and pool: + delete_pool(module, pool) + elif state == 'absent' and not pool: + module.exit_json(changed=False) + + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/infinidat/infini_vol.py b/lib/ansible/modules/storage/infinidat/infini_vol.py new file mode 100644 index 0000000000..40b03a5fee --- /dev/null +++ b/lib/ansible/modules/storage/infinidat/infini_vol.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com) +# +# 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: infini_vol +version_added: 2.3 +short_description: Create, Delete or Modify volumes on Infinibox +description: + - This module creates, deletes or modifies volume on Infinibox. +author: Gregory Shulov (@GR360RY) +options: + name: + description: + - Volume Name + required: true + state: + description: + - Creates/Modifies volume when present or removes when absent + required: false + default: present + choices: [ "present", "absent" ] + size: + description: + - Volume size in MB, GB or TB units. See examples. + required: false + pool: + description: + - Pool that volume will reside on + required: true +extends_documentation_fragment: + - infinibox +''' + +EXAMPLES = ''' +- name: Create new volume named foo under pool named bar + infini_vol: + name: foo + size: 1TB + pool: bar + state: present + user: admin + password: secret + system: ibox001 +''' + +RETURN = ''' +''' + +HAS_INFINISDK = True +try: + from infinisdk import InfiniBox, core +except ImportError: + HAS_INFINISDK = False + +from ansible.module_utils.infinibox import * +from capacity import KiB, Capacity + + +@api_wrapper +def get_pool(module, system): + """Return Pool or None""" + try: + return system.pools.get(name=module.params['pool']) + except: + return None + + +@api_wrapper +def get_volume(module, system): + """Return Volume or None""" + try: + return system.volumes.get(name=module.params['name']) + except: + return None + + +@api_wrapper +def create_volume(module, system): + """Create Volume""" + if not module.check_mode: + volume = system.volumes.create(name=module.params['name'], pool=get_pool(module, system)) + if module.params['size']: + size = Capacity(module.params['size']).roundup(64 * KiB) + volume.update_size(size) + module.exit_json(changed=True) + + +@api_wrapper +def update_volume(module, volume): + """Update Volume""" + changed = False + if module.params['size']: + size = Capacity(module.params['size']).roundup(64 * KiB) + if volume.get_size() != size: + if not module.check_mode: + volume.update_size(size) + changed = True + + module.exit_json(changed=changed) + + +@api_wrapper +def delete_volume(module, volume): + """ Delete Volume""" + if not module.check_mode: + volume.delete() + module.exit_json(changed=True) + + +def main(): + argument_spec = infinibox_argument_spec() + argument_spec.update( + dict( + name = dict(required=True), + state = dict(default='present', choices=['present', 'absent']), + pool = dict(required=True), + size = dict() + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_INFINISDK: + module.fail_json(msg='infinisdk is required for this module') + + if module.params['size']: + try: + Capacity(module.params['size']) + except: + module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units') + + state = module.params['state'] + system = get_system(module) + pool = get_pool(module, system) + volume = get_volume(module, system) + + if pool is None: + module.fail_json(msg='Pool {} not found'.format(module.params['pool'])) + + if state == 'present' and not volume: + create_volume(module, system) + elif state == 'present' and volume: + update_volume(module, volume) + elif state == 'absent' and volume: + delete_volume(module, volume) + elif state == 'absent' and not volume: + module.exit_json(changed=False) + + +# Import Ansible Utilities +from ansible.module_utils.basic import AnsibleModule +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/infinibox.py b/lib/ansible/utils/module_docs_fragments/infinibox.py new file mode 100644 index 0000000000..abc08b6eb6 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/infinibox.py @@ -0,0 +1,46 @@ +# +# (c) 2016, Gregory Shulov +# +# 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 . + + +class ModuleDocFragment(object): + + # Standard Infinibox documentation fragment + DOCUMENTATION = ''' +options: + system: + description: + - Infinibox Hostname or IPv4 Address. + required: true + user: + description: + - Infinibox User username with sufficient priveledges ( see notes ). + required: false + password: + description: + - Infinibox User password. + required: false +notes: + - This module requires infinisdk python library + - You must set INFINIBOX_USER and INFINIBOX_PASSWORD environment variables + if user and password arguments are not passed to the module directly + - Ansible uses the infinisdk configuration file C(~/.infinidat/infinisdk.ini) if no credentials are provided. + See U(http://infinisdk.readthedocs.io/en/latest/getting_started.html) +requirements: + - "python >= 2.7" + - infinisdk +''' diff --git a/test/compile/python2.4-skip.txt b/test/compile/python2.4-skip.txt index e8f7a8e6bb..b955de214d 100644 --- a/test/compile/python2.4-skip.txt +++ b/test/compile/python2.4-skip.txt @@ -24,6 +24,7 @@ /lib/ansible/modules/packaging/os/dnf.py /lib/ansible/modules/packaging/os/layman.py /lib/ansible/modules/remote_management/ipmi/ +/lib/ansible/modules/storage/infinidat/ /lib/ansible/modules/univention/ /lib/ansible/modules/web_infrastructure/letsencrypt.py /lib/ansible/module_utils/a10.py @@ -34,6 +35,7 @@ /lib/ansible/module_utils/gcdns.py /lib/ansible/module_utils/gce.py /lib/ansible/module_utils/gcp.py +/lib/ansible/module_utils/infinibox.py /lib/ansible/module_utils/lxd.py /lib/ansible/module_utils/openstack.py /lib/ansible/module_utils/rax.py