From 937d7993ccf6e5c65b68ec7456e03c87a1bcc7e6 Mon Sep 17 00:00:00 2001 From: Simon Dodsley Date: Fri, 16 Jun 2017 06:28:50 -0400 Subject: [PATCH] Initial commit for Pure Storage Ansible module (#25386) * Initial commit for Pure Storage Ansible module * Initial commit for Pure Storage Ansible module * Initial commit for Pure Storage Ansible module * Fix import issues as required by post-2.2 * Move last import to top * Follow suggestions and only implement one module per PR Fix documentation changes requested * Documentation and formatting changes --- CHANGELOG.md | 5 + .../dev_guide/developing_module_utilities.rst | 1 + lib/ansible/module_utils/pure.py | 77 +++++++ .../modules/storage/purestorage/__init__.py | 0 .../storage/purestorage/purefa_host.py | 207 ++++++++++++++++++ .../module_docs_fragments/purestorage.py | 40 ++++ 6 files changed, 330 insertions(+) create mode 100644 lib/ansible/module_utils/pure.py create mode 100644 lib/ansible/modules/storage/purestorage/__init__.py create mode 100644 lib/ansible/modules/storage/purestorage/purefa_host.py create mode 100644 lib/ansible/utils/module_docs_fragments/purestorage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b69a766be..887b1004c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -203,6 +203,11 @@ Ansible Changes By Release * gcp_healthcheck * gcp_target_proxy * gcp_url_map +- purestorage + * purefa_host + * purefa_volume + * purefa_hg + * purefa_pg - rundeck * rundeck_acl_policy * rundeck_project diff --git a/docs/docsite/rst/dev_guide/developing_module_utilities.rst b/docs/docsite/rst/dev_guide/developing_module_utilities.rst index 15a0b26ca6..b9cf790465 100644 --- a/docs/docsite/rst/dev_guide/developing_module_utilities.rst +++ b/docs/docsite/rst/dev_guide/developing_module_utilities.rst @@ -37,6 +37,7 @@ The following is a list of module_utils files and a general description. The mod - openstack.py - Utilities for modules that work with Openstack instances. - openswitch.py - Definitions and helper functions for modules that manage OpenSwitch devices - powershell.ps1 - Utilities for working with Microsoft Windows clients +- pure.py - Functions and utilities for modules that work with the Pure Storage storage platforms. - pycompat24.py - Exception workaround for Python 2.4. - rax.py - Definitions and helper functions for modules that work with Rackspace resources. - redhat.py - Functions for modules that manage Red Hat Network registration and subscriptions diff --git a/lib/ansible/module_utils/pure.py b/lib/ansible/module_utils/pure.py new file mode 100644 index 0000000000..a2b18e9c74 --- /dev/null +++ b/lib/ansible/module_utils/pure.py @@ -0,0 +1,77 @@ +# -*- 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), Simon Dodsley ,2017 +# 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_PURESTORAGE = True +try: + from purestorage import purestorage +except ImportError: + HAS_PURESTORAGE = False + +from functools import wraps +from os import environ +from os import path +import platform + +VERSION = 1.0 +USER_AGENT_BASE = 'Ansible' + + +def get_system(module): + """Return System Object or Fail""" + user_agent = '%(base)s %(class)s/%(version)s (%(platform)s)' % { + 'base': USER_AGENT_BASE, + 'class': __name__, + 'version': VERSION, + 'platform': platform.platform() + } + array_name = module.params['fa_url'] + api = module.params['api_token'] + + if array_name and api: + system = purestorage.FlashArray(array_name, api_token=api, user_agent=user_agent) + elif environ.get('PUREFA_URL') and environ.get('PUREFA_API'): + system = purestorage.FlashArray(environ.get('PUREFA_URL'), api_token=(environ.get('PUREFA_API')), user_agent=user_agent) + else: + module.fail_json(msg="You must set PUREFA_URL and PUREFA_API environment variables or the fa_url and api_token module arguments") + + try: + system.get() + except Exception: + module.fail_json(msg="Pure Storage FlashArray authentication failed. Check your credentials") + return system + + +def purefa_argument_spec(): + """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" + + return dict( + fa_url=dict(), + api_token=dict(no_log=True), + ) diff --git a/lib/ansible/modules/storage/purestorage/__init__.py b/lib/ansible/modules/storage/purestorage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/modules/storage/purestorage/purefa_host.py b/lib/ansible/modules/storage/purestorage/purefa_host.py new file mode 100644 index 0000000000..53c44a9596 --- /dev/null +++ b/lib/ansible/modules/storage/purestorage/purefa_host.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Simon Dodsley (simon@purestorage.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 = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: purefa_host +version_added: "2.4" +short_description: Create, Delete and Modify Hosts on Pure Storage FlashArray +description: + - This module creates, deletes or modifies hosts on Pure Storage FlashArray. +author: Simon Dodsley (@simondodsley) +options: + host: + description: + - Host Name + required: true + state: + description: + - Creates host. + - When removing host all connected volumes will be disconnected. + required: false + default: present + choices: [ "present", "absent" ] + protocol: + description: + - Defines the host connection protocol for volumes. + required: false + default: iscsi + choices: [ "iscsi", "fc" ] + wwns: + description: + - List of wwns of the host if protocol is fc + required: false + iqn: + description: + - List of IQNs of the host if protocol is iscsi + required: false + volume: + description: + - Volume name to map to the host + required: false +extends_documentation_fragment: + - purestorage +''' + +EXAMPLES = ''' +- name: Create new new host + purefa_host: + host: foo + fa_url: 10.10.10.2 + api_token: e31060a7-21fc-e277-6240-25983c6c4592 + +- name: Delete host + purefa_host: + host: foo + state: absent + fa_url: 10.10.10.2 + api_token: e31060a7-21fc-e277-6240-25983c6c4592 + +- name: Make sure host bar is available with wwn ports + purefa_host: + host: bar + protocol: fc + wwns: + - "00:00:00:00:00:00:00" + - "11:11:11:11:11:11:11" + fa_url: 10.10.10.2 + api_token: e31060a7-21fc-e277-6240-25983c6c4592 + +- name: Make sure host bar is available with iSCSI ports + purefa_host: + host: bar + protocol: iscsi + iqn: + - "iqn.1994-05.com.redhat:7d366003913" + fa_url: 10.10.10.2 + api_token: e31060a7-21fc-e277-6240-25983c6c4592 + +- name: Map host foo to volume bar + purefa_host: + host: foo + volume: bar + fa_url: 10.10.10.2 + api_token: e31060a7-21fc-e277-6240-25983c6c4592 +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.pure import get_system, purefa_argument_spec + + +HAS_PURESTORAGE = True +try: + from purestorage import purestorage +except ImportError: + HAS_PURESTORAGE = False + + +def get_host(module, array): + + host = None + + for h in array.list_hosts(): + if h["name"] == module.params['host']: + host = h + break + + return host + + +def make_host(module, array): + + changed = True + + if not module.check_mode: + host = array.create_host(module.params['host']) + if module.params['protocol'] == 'iscsi': + if module.params['iqn']: + array.set_host(module.params['host'], addiqnlist=module.params['iqn']) + if module.params['protocol'] == 'fc': + if module.params['wwns']: + array.set_host(module.params['host'], addwwnlist=module.params['wwns']) + if module.params['volume']: + array.connect_host(module.params['host'], module.params['volume']) + module.exit_json(changed=changed) + + +def update_host(module, array): + changed = False + host = module.params['host'] + module.exit_json(changed=changed) + + +def delete_host(module, array): + changed = True + if not module.check_mode: + for vol in array.list_host_connections(module.params['host']): + array.disconnect_host(module.params['host'], vol["vol"]) + array.delete_host(module.params['host']) + module.exit_json(changed=changed) + + +def main(): + argument_spec = purefa_argument_spec() + argument_spec.update( + dict( + host=dict(required=True), + state=dict(default='present', choices=['present', 'absent']), + protocol=dict(default='iscsi', choices=['iscsi', 'fc']), + iqn=dict(type='list'), + wwns=dict(type='list'), + volume=dict() + ) + ) + + module = AnsibleModule(argument_spec, supports_check_mode=True) + + if not HAS_PURESTORAGE: + module.fail_json(msg='purestorage sdk is required for this module in host') + + state = module.params['state'] + protocol = module.params['protocol'] + array = get_system(module) + host = get_host(module, array) + + if module.params['volume']: + try: + array.get_volume(module.params['volume']) + except: + module.fail_json(msg='Volume {} not found'.format(module.params['volume'])) + + if host and state == 'present': + update_host(module, array) + elif host and state == 'absent': + delete_host(module, array) + elif host is None and state == 'absent': + module.exit_json(changed=False) + else: + make_host(module, array) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/purestorage.py b/lib/ansible/utils/module_docs_fragments/purestorage.py new file mode 100644 index 0000000000..792d15fa34 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/purestorage.py @@ -0,0 +1,40 @@ +# +# (c) 2017, Simon Dodsley +# +# 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 Pure Storage documentation fragment + DOCUMENTATION = ''' +options: + fa_url: + description: + - FlashArray management IPv4 address or Hostname. + required: true + api_token: + description: + - FlashArray API token for admin privilaged user. + required: true +notes: + - This module requires purestorage python library + - You must set C(PUREFA_URL) and C(PUREFA_API) environment variables + if I(url) and I(api_token) arguments are not passed to the module directly +requirements: + - "python >= 2.7" + - purestorage +'''