From 6c3de3c299d4dfdac5303b069706371921c2e685 Mon Sep 17 00:00:00 2001
From: Gregory Shulov <gregory.shulov@gmail.com>
Date: Thu, 22 Dec 2016 15:18:19 +0200
Subject: [PATCH] Initial Commit for Infinidat Ansible Modules (#19429)

* Initial Commit for Infinidat Ansible Modules

Skip tests for python 2.4 as infinisdk doesn't support python 2.4

Move common code and arguments into module_utils/infinibox.py

Move common documentation to documentation_fragments. Cleanup Docs and Examples

Fix formating in modules description

Add check mode support for all modules

Import AnsibleModule only from ansible.module_utils.basic in all modules

Skip python 2.4 tests for module_utils/infinibox.py

Documentation and code cleanup

Rewrite examples in multiline format

Misc Changes

Test

* Add Infinibox modules to CHANGELOG.md

* Add ANSIBLE_METADATA to all modules
---
 CHANGELOG.md                                  |   7 +
 lib/ansible/module_utils/infinibox.py         |  93 ++++++++
 .../modules/storage/infinidat/__init__.py     |   0
 .../storage/infinidat/infini_export.py        | 200 ++++++++++++++++
 .../storage/infinidat/infini_export_client.py | 209 +++++++++++++++++
 .../modules/storage/infinidat/infini_fs.py    | 175 ++++++++++++++
 .../modules/storage/infinidat/infini_host.py  | 178 ++++++++++++++
 .../modules/storage/infinidat/infini_pool.py  | 219 ++++++++++++++++++
 .../modules/storage/infinidat/infini_vol.py   | 175 ++++++++++++++
 .../utils/module_docs_fragments/infinibox.py  |  46 ++++
 test/compile/python2.4-skip.txt               |   2 +
 11 files changed, 1304 insertions(+)
 create mode 100644 lib/ansible/module_utils/infinibox.py
 create mode 100644 lib/ansible/modules/storage/infinidat/__init__.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_export.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_export_client.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_fs.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_host.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_pool.py
 create mode 100644 lib/ansible/modules/storage/infinidat/infini_vol.py
 create mode 100644 lib/ansible/utils/module_docs_fragments/infinibox.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 823cb9419a3..aaf276987ea 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 00000000000..c16037e9a01
--- /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 <gregory.shulov@gmail.com>,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 00000000000..e69de29bb2d
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 00000000000..683234426aa
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..1dcde6305dd
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..0c43605f9c5
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..17b93e962f2
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..7fd4839503c
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..40b03a5feed
--- /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 <http://www.gnu.org/licenses/>.
+
+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 00000000000..abc08b6eb65
--- /dev/null
+++ b/lib/ansible/utils/module_docs_fragments/infinibox.py
@@ -0,0 +1,46 @@
+#
+# (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 <http://www.gnu.org/licenses/>.
+
+
+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 e8f7a8e6bba..b955de214dc 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