1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# (c) 2020, 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 9__metaclass__ = type 10 11 12ANSIBLE_METADATA = { 13 "metadata_version": "1.1", 14 "status": ["preview"], 15 "supported_by": "community", 16} 17 18 19DOCUMENTATION = """ 20--- 21module: purefa_network 22short_description: Manage network interfaces in a Pure Storage FlashArray 23version_added: '1.0.0' 24description: 25 - This module manages the physical and virtual network interfaces on a Pure Storage FlashArray. 26 - To manage VLAN interfaces use the I(purefa_vlan) module. 27 - To manage network subnets use the I(purefa_subnet) module. 28 - To remove an IP address from a non-management port use 0.0.0.0/0 29author: Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> 30options: 31 name: 32 description: 33 - Interface name (physical or virtual). 34 required: true 35 type: str 36 state: 37 description: 38 - State of existing interface (on/off). 39 required: false 40 default: present 41 choices: [ "present", "absent" ] 42 type: str 43 address: 44 description: 45 - IPv4 or IPv6 address of interface in CIDR notation. 46 - To remove an IP address from a non-management port use 0.0.0.0/0 47 required: false 48 type: str 49 gateway: 50 description: 51 - IPv4 or IPv6 address of interface gateway. 52 required: false 53 type: str 54 mtu: 55 description: 56 - MTU size of the interface. Range is 1280 to 9216. 57 required: false 58 default: 1500 59 type: int 60extends_documentation_fragment: 61 - purestorage.flasharray.purestorage.fa 62""" 63 64EXAMPLES = """ 65- name: Configure and enable network interface ct0.eth8 66 purefa_network: 67 name: ct0.eth8 68 gateway: 10.21.200.1 69 address: "10.21.200.18/24" 70 mtu: 9000 71 state: present 72 fa_url: 10.10.10.2 73 api_token: c6033033-fe69-2515-a9e8-966bb7fe4b40 74 75- name: Disable physical interface ct1.eth2 76 purefa_network: 77 name: ct1.eth2 78 state: absent 79 fa_url: 10.10.10.2 80 api_token: c6033033-fe69-2515-a9e8-966bb7fe4b40 81 82- name: Enable virtual network interface vir0 83 purefa_network: 84 name: vir0 85 state: present 86 fa_url: 10.10.10.2 87 api_token: c6033033-fe69-2515-a9e8-966bb7fe4b40 88 89- name: Remove an IP address from iSCSI interface ct0.eth4 90 purefa_network: 91 name: ct0.eth4 92 address: 0.0.0.0/0 93 gateway: 0.0.0.0 94 fa_url: 10.10.10.2 95 api_token: c6033033-fe69-2515-a9e8-966bb7fe4b40 96""" 97 98RETURN = """ 99""" 100 101try: 102 from netaddr import IPAddress, IPNetwork 103 104 HAS_NETADDR = True 105except ImportError: 106 HAS_NETADDR = False 107 108try: 109 from pypureclient.flasharray import NetworkInterfacePatch 110 111 HAS_PYPURECLIENT = True 112except ImportError: 113 HAS_PYPURECLIENT = False 114 115from ansible.module_utils.basic import AnsibleModule 116from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import ( 117 get_system, 118 get_array, 119 purefa_argument_spec, 120) 121 122FC_ENABLE_API = "2.4" 123 124 125def _get_fc_interface(module, array): 126 """Return FC Interface or None""" 127 interface = {} 128 interface_list = array.get_network_interfaces(names=[module.params["name"]]) 129 if interface_list.status_code == 200: 130 interface = list(interface_list.items)[0] 131 return interface 132 else: 133 return None 134 135 136def _get_interface(module, array): 137 """Return Network Interface or None""" 138 interface = {} 139 if module.params["name"][0] == "v": 140 try: 141 interface = array.get_network_interface(module.params["name"]) 142 except Exception: 143 return None 144 else: 145 try: 146 interfaces = array.list_network_interfaces() 147 except Exception: 148 return None 149 for ints in range(0, len(interfaces)): 150 if interfaces[ints]["name"] == module.params["name"]: 151 interface = interfaces[ints] 152 break 153 return interface 154 155 156def update_fc_interface(module, array, interface): 157 """Modify FC Interface settings""" 158 changed = False 159 if not interface.enabled and module.params["state"] == "present": 160 changed = True 161 if not module.check_mode: 162 network = NetworkInterfacePatch(enabled=True, override_npiv_check=True) 163 res = array.patch_network_interfaces( 164 names=[module.params["name"]], network=network 165 ) 166 if res.status_code != 200: 167 module.fail_json( 168 msg="Failed to enable interface {0}.".format(module.params["name"]) 169 ) 170 if interface.enabled and module.params["state"] == "absent": 171 changed = True 172 if not module.check_mode: 173 network = NetworkInterfacePatch(enabled=False, override_npiv_check=True) 174 res = array.patch_network_interfaces( 175 names=[module.params["name"]], network=network 176 ) 177 if res.status_code != 200: 178 module.fail_json( 179 msg="Failed to disable interface {0}.".format(module.params["name"]) 180 ) 181 module.exit_json(changed=changed) 182 183 184def update_interface(module, array, interface): 185 """Modify Interface settings""" 186 changed = False 187 current_state = { 188 "mtu": interface["mtu"], 189 "gateway": interface["gateway"], 190 "address": interface["address"], 191 "netmask": interface["netmask"], 192 } 193 if not module.params["address"]: 194 address = interface["address"] 195 else: 196 if module.params["gateway"]: 197 if module.params["gateway"] and module.params["gateway"] not in IPNetwork( 198 module.params["address"] 199 ): 200 module.fail_json(msg="Gateway and subnet are not compatible.") 201 elif not module.params["gateway"] and interface["gateway"] not in [ 202 None, 203 IPNetwork(module.params["address"]), 204 ]: 205 module.fail_json(msg="Gateway and subnet are not compatible.") 206 address = str(module.params["address"].split("/", 1)[0]) 207 if not module.params["mtu"]: 208 mtu = interface["mtu"] 209 else: 210 if not 1280 <= module.params["mtu"] <= 9216: 211 module.fail_json( 212 msg="MTU {0} is out of range (1280 to 9216)".format( 213 module.params["mtu"] 214 ) 215 ) 216 else: 217 mtu = module.params["mtu"] 218 if module.params["address"]: 219 netmask = str(IPNetwork(module.params["address"]).netmask) 220 else: 221 netmask = interface["netmask"] 222 if not module.params["gateway"]: 223 gateway = interface["gateway"] 224 else: 225 cidr = str(IPAddress(netmask).netmask_bits()) 226 full_addr = address + "/" + cidr 227 if module.params["gateway"] not in IPNetwork(full_addr): 228 module.fail_json(msg="Gateway and subnet are not compatible.") 229 gateway = module.params["gateway"] 230 new_state = { 231 "address": address, 232 "mtu": mtu, 233 "gateway": gateway, 234 "netmask": netmask, 235 } 236 if new_state != current_state: 237 changed = True 238 if ( 239 "management" in interface["services"] 240 or "app" in interface["services"] 241 and address == "0.0.0.0/0" 242 ): 243 module.fail_json( 244 msg="Removing IP address from a management or app port is not supported" 245 ) 246 if not module.check_mode: 247 try: 248 if new_state["gateway"] is not None: 249 array.set_network_interface( 250 interface["name"], 251 address=new_state["address"], 252 mtu=new_state["mtu"], 253 netmask=new_state["netmask"], 254 gateway=new_state["gateway"], 255 ) 256 else: 257 array.set_network_interface( 258 interface["name"], 259 address=new_state["address"], 260 mtu=new_state["mtu"], 261 netmask=new_state["netmask"], 262 ) 263 except Exception: 264 module.fail_json( 265 msg="Failed to change settings for interface {0}.".format( 266 interface["name"] 267 ) 268 ) 269 if not interface["enabled"] and module.params["state"] == "present": 270 changed = True 271 if not module.check_mode: 272 try: 273 array.enable_network_interface(interface["name"]) 274 except Exception: 275 module.fail_json( 276 msg="Failed to enable interface {0}.".format(interface["name"]) 277 ) 278 if interface["enabled"] and module.params["state"] == "absent": 279 changed = True 280 if not module.check_mode: 281 try: 282 array.disable_network_interface(interface["name"]) 283 except Exception: 284 module.fail_json( 285 msg="Failed to disable interface {0}.".format(interface["name"]) 286 ) 287 288 module.exit_json(changed=changed) 289 290 291def main(): 292 argument_spec = purefa_argument_spec() 293 argument_spec.update( 294 dict( 295 name=dict(type="str", required=True), 296 state=dict(type="str", default="present", choices=["present", "absent"]), 297 address=dict(type="str"), 298 gateway=dict(type="str"), 299 mtu=dict(type="int", default=1500), 300 ) 301 ) 302 303 module = AnsibleModule(argument_spec, supports_check_mode=True) 304 305 if not HAS_NETADDR: 306 module.fail_json(msg="netaddr module is required") 307 308 array = get_system(module) 309 api_version = array._list_available_rest_versions() 310 if module.params["name"].split(".")[1][0].lower() == "f": 311 if FC_ENABLE_API in api_version: 312 if not HAS_PYPURECLIENT: 313 module.fail_json(msg="pypureclient module is required") 314 array = get_array(module) 315 interface = _get_fc_interface(module, array) 316 if not interface: 317 module.fail_json(msg="Invalid network interface specified.") 318 else: 319 update_fc_interface(module, array, interface) 320 else: 321 module.warn("Purity version does not support enabling/disabling FC ports") 322 else: 323 interface = _get_interface(module, array) 324 if not interface: 325 module.fail_json(msg="Invalid network interface specified.") 326 else: 327 update_interface(module, array, interface) 328 329 module.exit_json(changed=False) 330 331 332if __name__ == "__main__": 333 main() 334