Coverage for plugins/modules/purefb_network.py: 64%
83 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-30 17:06 -0400
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-30 17:06 -0400
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
4# (c) 2017, Simon Dodsley (simon@everpuredata.com)
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7from __future__ import absolute_import, division, print_function
9__metaclass__ = type
12ANSIBLE_METADATA = {
13 "metadata_version": "1.1",
14 "status": ["preview"],
15 "supported_by": "community",
16}
19DOCUMENTATION = """
20---
21module: purefb_network
22version_added: "1.0.0"
23short_description: Manage network interfaces in a Everpure FlashBlade
24description:
25 - This module manages network interfaces on Everpure FlashBlade.
26 - When creating a network interface a subnet must already exist with
27 a network prefix that covers the IP address of the interface being
28 created.
29author: Everpure Ansible Team (@sdodsley) <pure-ansible-team@everpuredata.com>
30options:
31 name:
32 description:
33 - Interface Name.
34 required: true
35 type: str
36 state:
37 description:
38 - Create, delete or modifies a network interface.
39 required: false
40 default: present
41 choices: [ "present", "absent" ]
42 type: str
43 address:
44 description:
45 - IP address of interface.
46 required: false
47 type: str
48 services:
49 description:
50 - Define which services are configured for the interfaces.
51 required: false
52 choices: [ "data", "replication" ]
53 default: data
54 type: str
55 itype:
56 description:
57 - Type of interface.
58 required: false
59 choices: [ "vip" ]
60 default: vip
61 type: str
62 attached_server:
63 description:
64 - Name of the server you want to attach to the interface
65 - To attach to a server on a Realm, set the value to realm-1::server-1
66 - Only 1 server per Network Interface.
67 required: false
68 type: str
70extends_documentation_fragment:
71 - everpure.flashblade.everpure.fb
72"""
74EXAMPLES = """
75- name: Create new network interface named foo
76 everpure.flashblade.purefb_network:
77 name: foo
78 address: 10.21.200.23
79 state: present
80 fb_url: 10.10.10.2
81 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
83- name: Create a new network interface named foo with attached server named server-1 in a Realm named realm-1
84 everpure.flashblade.purefb_network:
85 name: foo
86 address: 10.21.200.23
87 attached_server: realm-1::server-1
88 state: present
89 fb_url: 10.10.10.2
90 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
92- name: Change IP address of network interface named foo
93 everpure.flashblade.purefb_network:
94 name: foo
95 state: present
96 address: 10.21.200.123
97 fb_url: 10.10.10.2
98 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
100- name: Delete network interface named foo
101 everpure.flashblade.purefb_network:
102 name: foo
103 state: absent
104 fb_url: 10.10.10.2
105 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
106"""
108RETURN = """
109"""
111HAS_PYPURECLIENT = True
112try:
113 from pypureclient.flashblade import NetworkInterface, NetworkInterfacePatch
114except ImportError:
115 HAS_PYPURECLIENT = False
117from ansible.module_utils.basic import AnsibleModule
118from ansible_collections.everpure.flashblade.plugins.module_utils.purefb import (
119 get_system,
120 purefb_argument_spec,
121)
122from ansible_collections.everpure.flashblade.plugins.module_utils.common import (
123 get_error_message,
124 get_rest_api_version,
125)
126from ansible_collections.everpure.flashblade.plugins.module_utils.version import (
127 LooseVersion,
128)
130SERVERS_API_VERSION = "2.16"
133def get_iface(module, blade):
134 """Return Filesystem or None"""
135 res = blade.get_network_interfaces(names=[module.params["name"]])
136 if res.status_code == 200:
137 return list(res.items)[0]
138 return None
141def _attached_server_name(iface):
142 """Name of the server an interface is attached to, or None.
144 ``attached_servers`` is a list of references on the API, even though an
145 interface can only carry one server.
146 """
147 attached = getattr(iface, "attached_servers", None) or []
148 if not attached:
149 return None
150 return getattr(attached[0], "name", None)
153def create_iface(module, blade):
154 """Create Network Interface"""
155 changed = True
156 if not module.check_mode:
157 network_interface = NetworkInterface(
158 address=module.params["address"],
159 services=[module.params["services"]],
160 type=module.params["itype"],
161 )
162 if module.params["attached_server"]:
163 network_interface.attached_servers = [
164 {"name": module.params["attached_server"]}
165 ]
166 res = blade.post_network_interfaces(
167 names=[module.params["name"]],
168 network_interface=network_interface,
169 )
170 if res.status_code != 200:
171 module.fail_json(
172 msg="Interface {0} creation failed. Error: {1}".format(
173 module.params["name"], get_error_message(res)
174 )
175 )
176 module.exit_json(changed=changed)
179def modify_iface(module, blade):
180 """Modify Network Interface IP address"""
181 changed = False
182 iface = get_iface(module, blade)
183 if module.params["address"] != iface.address:
184 changed = True
185 if not module.check_mode:
186 res = blade.patch_network_interfaces(
187 names=[module.params["name"]],
188 network_interface=NetworkInterfacePatch(
189 address=module.params["address"]
190 ),
191 )
192 if res.status_code != 200:
193 module.fail_json(
194 msg="Failed to modify Interface {0}. Error: {1}".format(
195 module.params["name"], get_error_message(res)
196 )
197 )
198 # Only move the interface when it is not already on the requested server.
199 # An interface carries at most one attached server, but the API field is
200 # a list, so the current name has to be pulled out of it.
201 wanted_server = module.params["attached_server"]
202 if wanted_server is not None and wanted_server != _attached_server_name(iface):
203 changed = True
204 if not module.check_mode:
205 res = blade.patch_network_interfaces(
206 names=[module.params["name"]],
207 network_interface=NetworkInterfacePatch(
208 attached_servers=[{"name": module.params["attached_server"]}]
209 ),
210 )
211 if res.status_code != 200:
212 module.fail_json(
213 msg="Failed to modify Interface {0}. Error: {1}".format(
214 module.params["name"], get_error_message(res)
215 )
216 )
217 module.exit_json(changed=changed)
220def delete_iface(module, blade):
221 """Delete Network Interface"""
222 changed = True
223 if not module.check_mode:
224 res = blade.delete_network_interfaces(names=[module.params["name"]])
225 if res.status_code != 200:
226 module.fail_json(
227 msg="Failed to delete network {0}. Error: {1}".format(
228 module.params["name"], get_error_message(res)
229 )
230 )
231 module.exit_json(changed=changed)
234def main():
235 argument_spec = purefb_argument_spec()
236 argument_spec.update(
237 dict(
238 name=dict(type="str", required=True),
239 state=dict(type="str", default="present", choices=["present", "absent"]),
240 address=dict(type="str"),
241 services=dict(type="str", default="data", choices=["data", "replication"]),
242 itype=dict(type="str", default="vip", choices=["vip"]),
243 attached_server=dict(type="str", required=False),
244 )
245 )
247 required_if = [["state", "present", ["address"]]]
249 module = AnsibleModule(
250 argument_spec, required_if=required_if, supports_check_mode=True
251 )
253 if not HAS_PYPURECLIENT:
254 module.fail_json(msg="py-pure-client sdk is required for this module")
255 state = module.params["state"]
256 blade = get_system(module)
257 iface = get_iface(module, blade)
259 api_version = get_rest_api_version(blade)
260 if module.params["attached_server"] and LooseVersion(
261 SERVERS_API_VERSION
262 ) > LooseVersion(api_version):
263 module.fail_json(
264 msg="FlashBlade REST version {0} does not support attached_server "
265 "(requires {1}+).".format(api_version, SERVERS_API_VERSION)
266 )
268 if state == "present" and not iface:
269 create_iface(module, blade)
270 elif state == "present" and iface:
271 modify_iface(module, blade)
272 elif state == "absent" and iface:
273 delete_iface(module, blade)
274 elif state == "absent" and not iface:
275 module.exit_json(changed=False)
278if __name__ == "__main__":
279 main()