1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# (c) 2017, Simon Dodsley (simon@purestorage.com)
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15
16DOCUMENTATION = '''
17---
18module: purefb_subnet
19version_added: "2.8"
20short_description:  Manage network subnets in a Pure Storage FlashBlade
21description:
22    - This module manages network subnets on Pure Storage FlashBlade.
23author: Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
24options:
25  name:
26    description:
27      - Subnet Name.
28    required: true
29    type: str
30  state:
31    description:
32      - Create, delete or modifies a subnet.
33    required: false
34    default: present
35    choices: [ "present", "absent" ]
36    type: str
37  gateway:
38    description:
39      - IPv4 or IPv6 address of subnet gateway.
40    required: false
41    type: str
42  mtu:
43    description:
44      - MTU size of the subnet. Range is 1280 to 9216.
45    required: false
46    default: 1500
47    type: int
48  prefix:
49    description:
50      - IPv4 or IPv6 address associated with the subnet.
51      - Supply the prefix length (CIDR) as well as the IP address.
52    required: false
53    type: str
54  vlan:
55    description:
56      - VLAN ID of the subnet.
57    required: false
58    default: 0
59    type: int
60extends_documentation_fragment:
61    - purestorage.fb
62notes:
63    - Requires the netaddr Python package on the host.
64requirements:
65    - netaddr
66'''
67
68EXAMPLES = '''
69- name: Create new network subnet named foo
70  purefb_subnet:
71    name: foo
72    prefix: "10.21.200.3/24"
73    gateway: 10.21.200.1
74    mtu: 9000
75    vlan: 2200
76    state: present
77    fb_url: 10.10.10.2
78    api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
79
80- name: Change configuration of existing subnet foo
81  purefb_network:
82    name: foo
83    state: present
84    prefix: "10.21.100.3/24"
85    gateway: 10.21.100.1
86    mtu: 1500
87    address: 10.21.200.123
88    fb_url: 10.10.10.2
89    api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
90
91- name: Delete network subnet named foo
92  purefb_subnet:
93    name: foo
94    state: absent
95    fb_url: 10.10.10.2
96    api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641'''
97
98RETURN = '''
99'''
100
101HAS_PURITY_FB = True
102try:
103    from purity_fb import Subnet
104except ImportError:
105    HAS_PURITY_FB = False
106
107try:
108    import netaddr
109    HAS_NETADDR = True
110except ImportError:
111    HAS_NETADDR = False
112
113from ansible.module_utils.basic import AnsibleModule
114from ansible.module_utils.pure import get_blade, purefb_argument_spec
115
116
117MINIMUM_API_VERSION = '1.3'
118
119
120def get_subnet(module, blade):
121    """Return Subnet or None"""
122    subnet = []
123    subnet.append(module.params['name'])
124    try:
125        res = blade.subnets.list_subnets(names=subnet)
126        return res.items[0]
127    except Exception:
128        return None
129
130
131def create_subnet(module, blade):
132    """Create Subnet"""
133
134    subnet = []
135    subnet.append(module.params['name'])
136    try:
137        blade.subnets.create_subnets(names=subnet,
138                                     subnet=Subnet(prefix=module.params['prefix'],
139                                                   vlan=module.params['vlan'],
140                                                   mtu=module.params['mtu'],
141                                                   gateway=module.params['gateway']
142                                                   )
143                                     )
144        changed = True
145    except Exception:
146        module.fail_json(msg='Failed to create subnet {0}. Confirm supplied parameters'.format(module.params['name']))
147    module.exit_json(changed=changed)
148
149
150def modify_subnet(module, blade):
151    """Modify Subnet settings"""
152    changed = False
153    subnet = get_subnet(module, blade)
154    subnet_new = []
155    subnet_new.append(module.params['name'])
156    if module.params['prefix']:
157        if module.params['prefix'] != subnet.prefix:
158            try:
159                blade.subnets.update_subnets(names=subnet_new,
160                                             subnet=Subnet(prefix=module.params['prefix']))
161                changed = True
162            except Exception:
163                module.fail_json(msg='Failed to change subnet {0} prefix to {1}'.format(module.params['name'],
164                                                                                        module.params['prefix']))
165    if module.params['vlan']:
166        if module.params['vlan'] != subnet.vlan:
167            try:
168                blade.subnets.update_subnets(names=subnet_new,
169                                             subnet=Subnet(vlan=module.params['vlan']))
170                changed = True
171            except Exception:
172                module.fail_json(msg='Failed to change subnet {0} VLAN to {1}'.format(module.params['name'],
173                                                                                      module.params['vlan']))
174    if module.params['gateway']:
175        if module.params['gateway'] != subnet.gateway:
176            try:
177                blade.subnets.update_subnets(names=subnet_new,
178                                             subnet=Subnet(gateway=module.params['gateway']))
179                changed = True
180            except Exception:
181                module.fail_json(msg='Failed to change subnet {0} gateway to {1}'.format(module.params['name'],
182                                                                                         module.params['gateway']))
183    if module.params['mtu']:
184        if module.params['mtu'] != subnet.mtu:
185            try:
186                blade.subnets.update_subnets(names=subnet_new,
187                                             subnet=Subnet(mtu=module.params['mtu']))
188                changed = True
189            except Exception:
190                module.fail_json(msg='Failed to change subnet {0} MTU to {1}'.format(module.params['name'],
191                                                                                     module.params['mtu']))
192    module.exit_json(changed=changed)
193
194
195def delete_subnet(module, blade):
196    """ Delete Subnet"""
197    subnet = []
198    subnet.append(module.params['name'])
199    try:
200        blade.subnets.delete_subnets(names=subnet)
201        changed = True
202    except Exception:
203        changed = False
204    module.exit_json(changed=changed)
205
206
207def main():
208    argument_spec = purefb_argument_spec()
209    argument_spec.update(
210        dict(
211            name=dict(required=True),
212            state=dict(default='present', choices=['present', 'absent']),
213            gateway=dict(),
214            mtu=dict(type='int', default=1500),
215            prefix=dict(),
216            vlan=dict(type='int', default=0),
217        )
218    )
219
220    required_if = [["state", "present", ["gateway", 'prefix']]]
221
222    module = AnsibleModule(argument_spec,
223                           required_if=required_if,
224                           supports_check_mode=False)
225
226    if not HAS_PURITY_FB:
227        module.fail_json(msg='purity_fb sdk is required for this module')
228
229    if not HAS_NETADDR:
230        module.fail_json(msg='netaddr module is required')
231
232    state = module.params['state']
233    blade = get_blade(module)
234    api_version = blade.api_version.list_versions().versions
235    if MINIMUM_API_VERSION not in api_version:
236        module.fail_json(msg='Upgrade Purity//FB to enable this module')
237    subnet = get_subnet(module, blade)
238    if state == 'present':
239        if not (1280 <= module.params['mtu'] <= 9216):
240            module.fail_json(msg='MTU {0} is out of range (1280 to 9216)'.format(module.params['mtu']))
241        if not (0 <= module.params['vlan'] <= 4094):
242            module.fail_json(msg='VLAN ID {0} is out of range (0 to 4094)'.format(module.params['vlan']))
243        if netaddr.IPAddress(module.params['gateway']) not in netaddr.IPNetwork(module.params['prefix']):
244            module.fail_json(msg='Gateway and subnet are not compatible.')
245        subnets = blade.subnets.list_subnets()
246        nrange = netaddr.IPSet([module.params['prefix']])
247        for sub in range(0, len(subnets.items)):
248            if subnets.items[sub].vlan == module.params['vlan'] and subnets.items[sub].name != module.params['name']:
249                module.fail_json(msg='VLAN ID {0} is already in use.'.format(module.params['vlan']))
250            if nrange & netaddr.IPSet([subnets.items[sub].prefix]) and subnets.items[sub].name != module.params['name']:
251                module.fail_json(msg='Prefix CIDR overlaps with existing subnet.')
252
253    if state == 'present' and not subnet:
254        create_subnet(module, blade)
255    elif state == 'present' and subnet:
256        modify_subnet(module, blade)
257    elif state == 'absent' and subnet:
258        delete_subnet(module, blade)
259    elif state == 'absent' and not subnet:
260        module.exit_json(changed=False)
261
262
263if __name__ == '__main__':
264    main()
265