diff --git a/lib/ansible/module_utils/pure.py b/lib/ansible/module_utils/pure.py index a2b18e9c744..a5d63ded689 100644 --- a/lib/ansible/module_utils/pure.py +++ b/lib/ansible/module_utils/pure.py @@ -34,12 +34,18 @@ try: except ImportError: HAS_PURESTORAGE = False +HAS_PURITY_FB = True +try: + from purity_fb import PurityFb, FileSystem, FileSystemSnapshot, SnapshotSuffix, rest +except ImportError: + HAS_PURITY_FB = False + from functools import wraps from os import environ from os import path import platform -VERSION = 1.0 +VERSION = 1.1 USER_AGENT_BASE = 'Ansible' @@ -60,7 +66,6 @@ def get_system(module): 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: @@ -68,6 +73,37 @@ def get_system(module): return system +def get_blade(module): + """Return System Object or Fail""" +# Note: user_agent not included in FlashBlade API 1.1 +# user_agent = '%(base)s %(class)s/%(version)s (%(platform)s)' % { +# 'base': USER_AGENT_BASE, +# 'class': __name__, +# 'version': VERSION, +# 'platform': platform.platform() +# } + blade_name = module.params['fb_url'] + api = module.params['api_token'] + + if blade_name and api: + blade = PurityFb(blade_name) + blade.disable_verify_ssl() + try: + blade.login(api) + except rest.ApiException as e: + module.fail_json(msg="Pure Storage FlashBlade authentication failed. Check your credentials") + elif environ.get('PUREFB_URL') and environ.get('PUREFB_API'): + blade = PurityFb(environ.get('PUREFB_URL')) + blade.disable_verify_ssl() + try: + blade.login(environ.get('PUREFB_API')) + except rest.ApiException as e: + module.fail_json(msg="Pure Storage FlashBlade authentication failed. Check your credentials") + else: + module.fail_json(msg="You must set PUREFB_URL and PUREFB_API environment variables or the fb_url and api_token module arguments") + return blade + + def purefa_argument_spec(): """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" @@ -75,3 +111,12 @@ def purefa_argument_spec(): fa_url=dict(), api_token=dict(no_log=True), ) + + +def purefb_argument_spec(): + """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" + + return dict( + fb_url=dict(), + api_token=dict(no_log=True), + ) diff --git a/lib/ansible/modules/storage/purestorage/purefa_hg.py b/lib/ansible/modules/storage/purestorage/purefa_hg.py index aae9d25cff1..fb8c38ea9fb 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_hg.py +++ b/lib/ansible/modules/storage/purestorage/purefa_hg.py @@ -37,7 +37,7 @@ options: description: - List of existing volumes to add to hostgroup. extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefa_host.py b/lib/ansible/modules/storage/purestorage/purefa_host.py index 017c68e42d7..503cc6b3cfd 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_host.py +++ b/lib/ansible/modules/storage/purestorage/purefa_host.py @@ -46,7 +46,7 @@ options: description: - Volume name to map to the host. extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefa_pg.py b/lib/ansible/modules/storage/purestorage/purefa_pg.py index eda607066c5..d287297f13c 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_pg.py +++ b/lib/ansible/modules/storage/purestorage/purefa_pg.py @@ -50,7 +50,7 @@ options: type : bool default: 'yes' extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefa_pgsnap.py b/lib/ansible/modules/storage/purestorage/purefa_pgsnap.py index a9f1ec60970..f40546c6a04 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_pgsnap.py +++ b/lib/ansible/modules/storage/purestorage/purefa_pgsnap.py @@ -39,7 +39,7 @@ options: type: bool default: 'no' extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefa_snap.py b/lib/ansible/modules/storage/purestorage/purefa_snap.py index 0b88d6a851b..e70b6a17c16 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_snap.py +++ b/lib/ansible/modules/storage/purestorage/purefa_snap.py @@ -47,7 +47,7 @@ options: type: bool default: 'no' extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefa_volume.py b/lib/ansible/modules/storage/purestorage/purefa_volume.py index 3ea3a95f160..d547b4dbc0d 100644 --- a/lib/ansible/modules/storage/purestorage/purefa_volume.py +++ b/lib/ansible/modules/storage/purestorage/purefa_volume.py @@ -47,7 +47,7 @@ options: description: - Volume size in M, G, T or P units. extends_documentation_fragment: -- purestorage +- purestorage.fa ''' EXAMPLES = r''' diff --git a/lib/ansible/modules/storage/purestorage/purefb_fs.py b/lib/ansible/modules/storage/purestorage/purefb_fs.py new file mode 100644 index 00000000000..dac71e001e7 --- /dev/null +++ b/lib/ansible/modules/storage/purestorage/purefb_fs.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Simon Dodsley (simon@purestorage.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: purefb_fs +version_added: "2.6" +short_description: Manage filesystemon Pure Storage FlashBlade` +description: + - This module manages filesystems on Pure Storage FlashBlade. +author: Simon Dodsley (@simondodsley) +options: + name: + description: + - Filesystem Name. + required: true + state: + description: + - Create, delete or modifies a filesystem. + required: false + default: present + choices: [ "present", "absent" ] + eradicate: + description: + - Define whether to eradicate the filesystem on delete or leave in trash. + required: false + type: bool + default: false + size: + description: + - Volume size in M, G, T or P units. See examples. + required: false + default: 32G + nfs: + description: + - Define whether to NFS protocol is enabled for the filesystem. + required: false + type: bool + default: true + nfs_rules: + description: + - Define the NFS rules in operation. + required: false + default: '*(rw,no_root_squash)' + smb: + description: + - Define whether to SMB protocol is enabled for the filesystem. + required: false + type: bool + default: false + http: + description: + - Define whether to HTTP/HTTPS protocol is enabled for the filesystem. + required: false + type: bool + default: false + snapshot: + description: + - Define whether a snapshot directory is enabled for the filesystem. + required: false + type: bool + default: false + fastremove: + description: + - Define whether the fast remove directory is enabled for the filesystem. + required: false + type: bool + default: false +extends_documentation_fragment: + - purestorage.fb +''' + +EXAMPLES = ''' +- name: Create new filesystem named foo + purefb_fs: + name: foo + size: 1T + state: present + fb_url: 10.10.10.2 + api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 + +- name: Delete filesystem named foo + purefb_fs: + name: foo + state: absent + fb_url: 10.10.10.2 + api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 + +- name: Recover filesystem named foo + purefb_fs: + name: foo + state: present + fb_url: 10.10.10.2 + api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 + +- name: Eradicate filesystem named foo + purefb_fs: + name: foo + state: absent + eradicate: true + fb_url: 10.10.10.2 + api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 + +- name: Modify attributes of an existing filesystem named foo + purefb_fs: + name: foo + + size: 2T + nfs : true + nfs_rules: '*(ro)' + snapshot: true + fastremove: true + smb: true + state: present + fb_url: 10.10.10.2 + api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641''' + +RETURN = ''' +''' + +HAS_PURITY_FB = True +try: + from purity_fb import FileSystem, ProtocolRule, NfsRule +except ImportError: + HAS_PURITY_FB = False + +from ansible.module_utils.basic import AnsibleModule, human_to_bytes +from ansible.module_utils.pure import get_blade, purefb_argument_spec + + +def get_fs(module, blade): + """Return Filesystem or None""" + fs = [] + fs.append(module.params['name']) + try: + res = blade.file_systems.list_file_systems(names=fs) + return res.items[0] + except: + return None + + +def create_fs(module, blade): + """Create Filesystem""" + + if not module.params['size']: + module.params['size'] = '32G' + + size = human_to_bytes(module.params['size']) + + if not module.check_mode: + try: + fs_obj = FileSystem(name=module.params['name'], + provisioned=size, + fast_remove_directory_enabled=module.params['fastremove'], + snapshot_directory_enabled=module.params['snapshot'], + nfs=NfsRule(enabled=module.params['nfs'], rules=module.params['nfs_rules']), + smb=ProtocolRule(enabled=module.params['smb']), + http=ProtocolRule(enabled=module.params['http']) + ) + blade.file_systems.create_file_systems(fs_obj) + changed = True + except: + changed = False + module.exit_json(changed=changed) + + +def modify_fs(module, blade): + """Modify Filesystem""" + changed = False + attr = {} + if not module.check_mode: + fs = get_fs(module, blade) + if fs.destroyed: + attr['destroyed'] = False + changed = True + if module.params['size']: + if human_to_bytes(module.params['size']) > fs.provisioned: + attr['provisioned'] = human_to_bytes(module.params['size']) + changed = True + if module.params['nfs'] and not fs.nfs.enabled: + attr['nfs'] = NfsRule(enabled=module.params['nfs']) + changed = True + if not module.params['nfs'] and fs.nfs.enabled: + attr['nfs'] = NfsRule(enabled=module.params['nfs']) + changed = True + if module.params['nfs'] and fs.nfs.enabled: + if fs.nfs.rules != module.params['nfs_rules']: + attr['nfs'] = NfsRule(rules=module.params['nfs_rules']) + changed = True + if module.params['smb'] and not fs.smb.enabled: + attr['smb'] = ProtocolRule(enabled=module.params['smb']) + changed = True + if not module.params['smb'] and fs.smb.enabled: + attr['smb'] = ProtocolRule(enabled=module.params['smb']) + changed = True + if module.params['http'] and not fs.http.enabled: + attr['http'] = ProtocolRule(enabled=module.params['http']) + changed = True + if not module.params['http'] and fs.http.enabled: + attr['http'] = ProtocolRule(enabled=module.params['http']) + changed = True + if module.params['snapshot'] and not fs.snapshot_directory_enabled: + attr['snapshot_directory_enabled'] = module.params['snapshot'] + changed = True + if not module.params['snapshot'] and fs.snapshot_directory_enabled: + attr['snapshot_directory_enabled'] = module.params['snapshot'] + changed = True + if module.params['fastremove'] and not fs.fast_remove_directory_enabled: + attr['fast_remove_directory_enabled'] = module.params['fastremove'] + changed = True + if not module.params['fastremove'] and fs.fast_remove_directory_enabled: + attr['fast_remove_directory_enabled'] = module.params['fastremove'] + changed = True + if changed: + n_attr = FileSystem(**attr) + try: + blade.file_systems.update_file_systems(name=module.params['name'], attributes=n_attr) + except: + changed = False + module.exit_json(changed=changed) + + +def delete_fs(module, blade): + """ Delete Filesystem""" + if not module.check_mode: + try: + blade.file_systems.update_file_systems(name=module.params['name'], + attributes=FileSystem(nfs=NfsRule(enabled=False), + smb=ProtocolRule(enabled=False), + http=ProtocolRule(enabled=False), + destroyed=True) + ) + changed = True + if module.params['eradicate']: + try: + blade.file_systems.delete_file_systems(module.params['name']) + changed = True + except: + changed = False + except: + changed = False + module.exit_json(changed=changed) + + +def eradicate_fs(module, blade): + """ Eradicate Filesystem""" + if not module.check_mode: + try: + blade.file_systems.delete_file_systems(module.params['name']) + changed = True + except: + changed = False + module.exit_json(changed=changed) + + +def main(): + argument_spec = purefb_argument_spec() + argument_spec.update( + dict( + name=dict(required=True), + eradicate=dict(default='false', type='bool'), + nfs=dict(default='true', type='bool'), + nfs_rules=dict(default='*(rw,no_root_squash)'), + smb=dict(default='false', type='bool'), + http=dict(default='false', type='bool'), + snapshot=dict(default='false', type='bool'), + fastremove=dict(default='false', type='bool'), + state=dict(default='present', choices=['present', 'absent']), + size=dict() + ) + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True) + + if not HAS_PURITY_FB: + module.fail_json(msg='purity_fb sdk is required for this module') + + state = module.params['state'] + blade = get_blade(module) + fs = get_fs(module, blade) + + if state == 'present' and not fs: + create_fs(module, blade) + elif state == 'present' and fs: + modify_fs(module, blade) + elif state == 'absent' and fs and not fs.destroyed: + delete_fs(module, blade) + elif state == 'absent' and fs and fs.destroyed: + eradicate_fs(module, blade) + elif state == 'absent' and not fs: + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/purestorage.py b/lib/ansible/utils/module_docs_fragments/purestorage.py index 792d15fa344..71c01c5ce48 100644 --- a/lib/ansible/utils/module_docs_fragments/purestorage.py +++ b/lib/ansible/utils/module_docs_fragments/purestorage.py @@ -21,6 +21,34 @@ class ModuleDocFragment(object): # Standard Pure Storage documentation fragment DOCUMENTATION = ''' +options: + - See seperate platform section for more details +requirements: + - See seperate platform section for more details +notes: + - Ansible modules are available for the following Pure Storage products: FlashArray, FlashBlade +''' + + # Documentation fragment for FlashBlade + FB = ''' +options: + fb_url: + description: + - FlashBlade management IP address or Hostname. + api_token: + description: + - FlashBlade API token for admin privilaed user. +notes: + - This module requires the ```purity_fb``` python library + - You must set PUREFB_URL and PUREFB_API environment variables + if fb_url and api_token arguments are not passed to the module directly +requirements: + - "python >= 2.7" + - "purity_fb >= 1.1" +''' + + # Documentation fragment for FlashArray + FA = ''' options: fa_url: description: