1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2021, 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 = r"""
---
module: purefb_lag
version_added: '1.7.0'
short_description: Manage FlashBlade Link Aggregation Groups
description:
- Maintain FlashBlade Link Aggregation Groups
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- Name of the Link Aggregation Group
type: str
default: uplink
state:
description:
- Define whether the LAG should be added or deleted
default: present
choices: [ absent, present ]
type: str
ports:
description:
- Name of network ports assigned to the LAG
- Format should be CHx.ETHy, where CHx is the chassis number and
ETHy is the ethernet port number.
- Matched port pairs from each Fabric Module in the Chassis will
be used.
- To modify required ports for a LAG specify only the ports required
by the LAG. Any ports currently used by the LAG not specified will be
disconnected from the LAG.
type: list
elements: str
extends_documentation_fragment:
- purestorage.flashblade.purestorage.fb
"""
EXAMPLES = r"""
- name: Add LAG
purestorage.flashblade.purefb_lag:
name: lag2
ports:
- ch1.eth2
fb_url: 10.10.10.2
api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3
- name: Upate LAG
purestorage.flashblade.purefb_lag:
name: lag2
ports:
- ch1.eth2
- ch1.eth4
fb_url: 10.10.10.2
api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3
- name: Delete LAG
purestorage.flashblade.purefb_lag:
name: lag2
state: absent
fb_url: 10.10.10.2
api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3
"""
RETURN = r"""
lag:
description: A dictionary describing the LAG.
type: dict
returned: success
contains:
lag_speed:
description: Combined speed of all ports in the LAG in Gb/s
type: str
port_speed:
description: Configured speed of each port in the LAG in Gb/s
type: str
mac_address:
description: Unique MAC address assigned to the LAG
type: str
status:
description: Health status of the LAG.
type: str
"""
HAS_PURESTORAGE = True
try:
from pypureclient import flashblade
except ImportError:
HAS_PURESTORAGE = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flashblade.plugins.module_utils.purefb import (
get_system,
purefb_argument_spec,
)
MIN_REQUIRED_API_VERSION = "2.0"
def delete_lag(module, blade):
"""Delete Link Aggregation Group"""
changed = True
if not module.check_mode:
res = blade.delete_link_aggregation_groups(names=[module.params["name"]])
if res.status_code != 200:
module.fail_json(
msg="Failed to delete LAG {0}. Error: {1}".format(
module.params["name"], res.errors[0].message
)
)
module.exit_json(changed=changed)
def update_lag(module, blade):
"""Update Link Aggregation Group"""
changed = False
used_ports = []
current_ports = []
lagfact = []
current_lag = list(
blade.get_link_aggregation_groups(names=[module.params["name"]]).items
)[0]
for port in range(0, len(current_lag.ports)):
used_ports.append(current_lag.ports[port].name)
for lag_port in range(0, len(module.params["ports"]), 2):
if (
not (
module.params["ports"][lag_port].split(".")[0].upper()
+ ".FM1."
+ module.params["ports"][lag_port].split(".")[1].upper()
)
in used_ports
):
current_lags = list(blade.get_link_aggregation_groups().items)
for lag in range(0, len(current_lags)):
for port in range(0, len(current_lags[lag].ports)):
current_ports.append(current_lags[lag].ports[port].name)
for current_lag_port in range(0, len(current_ports)):
if (
module.params["ports"][lag_port].split(".")[0].upper()
+ ".FM1."
+ module.params["ports"][lag_port].split(".")[1].upper()
) in current_ports:
module.fail_json(
msg="Selected port {0} is currently in use by another LAG.".format(
module.params["ports"][lag_port].upper()
)
)
new_ports = []
for port in range(0, len(module.params["ports"])):
if module.params["ports"][port].split(".")[0].upper()[0] != "X":
new_ports.append(
module.params["ports"][port].split(".")[0].upper()
+ ".FM1."
+ module.params["ports"][port].split(".")[1].upper()
)
new_ports.append(
module.params["ports"][port].split(".")[0].upper()
+ ".FM2."
+ module.params["ports"][port].split(".")[1].upper()
)
else:
new_ports.append(module.params["ports"][port].upper())
ports = []
for final_port in range(0, len(new_ports)):
ports.append(flashblade.FixedReference(name=new_ports[final_port]))
link_aggregation_group = flashblade.Linkaggregationgroup(ports=ports)
if sorted(used_ports) != sorted(new_ports):
changed = True
if not module.check_mode:
res = blade.patch_link_aggregation_groups(
names=[module.params["name"]],
link_aggregation_group=link_aggregation_group,
)
if res.status_code != 200:
module.fail_json(
msg="Failed to update LAG {0}. Error: {1}".format(
module.params["name"],
res.errors[0].message,
)
)
else:
response = list(res.items)[0]
lagfact = {
"mac_address": response.mac_address,
"port_speed": str(response.port_speed / 1000000000) + "Gb/s",
"lag_speed": str(response.lag_speed / 1000000000) + "Gb/s",
"status": response.status,
}
module.exit_json(changed=changed, lag=lagfact)
def create_lag(module, blade):
"""Create Link Aggregation Group"""
changed = True
used_ports = []
lagfact = []
current_lags = list(blade.get_link_aggregation_groups().items)
for lag in range(0, len(current_lags)):
for port in range(0, len(current_lags[lag].ports)):
used_ports.append(current_lags[lag].ports[port].name)
for lag_port in range(0, len(module.params["ports"])):
if (
module.params["ports"][lag_port].split(".")[0].upper()
+ ".FM1."
+ module.params["ports"][0].split(".")[1].upper()
) in used_ports:
module.fail_json(
msg="Selected port {0} is currently in use by another LAG.".format(
module.params["ports"][lag_port].upper()
)
)
new_ports = []
for new_port in range(0, len(module.params["ports"])):
new_ports.append(
module.params["ports"][new_port].split(".")[0].upper()
+ ".FM1."
+ module.params["ports"][new_port].split(".")[1].upper()
)
new_ports.append(
module.params["ports"][new_port].split(".")[0].upper()
+ ".FM2."
+ module.params["ports"][new_port].split(".")[1].upper()
)
ports = []
for final_port in range(0, len(new_ports)):
ports.append(flashblade.FixedReference(name=new_ports[final_port]))
link_aggregation_group = flashblade.LinkAggregationGroup(ports=ports)
if not module.check_mode:
res = blade.post_link_aggregation_groups(
names=[module.params["name"]], link_aggregation_group=link_aggregation_group
)
if res.status_code != 200:
module.fail_json(
msg="Failed to create LAG {0}. Error: {1}".format(
module.params["name"],
res.errors[0].message,
)
)
else:
response = list(res.items)[0]
lagfact = {
"mac_address": response.mac_address,
"port_speed": str(response.port_speed / 1000000000) + "Gb/s",
"lag_speed": str(response.lag_speed / 1000000000) + "Gb/s",
"status": response.status,
}
module.exit_json(changed=changed, lag=lagfact)
def main():
argument_spec = purefb_argument_spec()
argument_spec.update(
dict(
state=dict(type="str", default="present", choices=["absent", "present"]),
name=dict(type="str", default="uplink"),
ports=dict(type="list", elements="str"),
)
)
required_if = [["state", "present", ["ports"]]]
module = AnsibleModule(
argument_spec, required_if=required_if, supports_check_mode=True
)
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
blade = get_system(module)
api_version = list(blade.get_versions().items)
if MIN_REQUIRED_API_VERSION not in api_version:
module.fail_json(
msg="FlashBlade REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
state = module.params["state"]
exists = bool(
blade.get_link_aggregation_groups(names=[module.params["name"]]).status_code
== 200
)
if module.params["ports"]:
# Remove duplicates
module.params["ports"] = list(dict.fromkeys(module.params["ports"]))
if not exists and state == "present":
create_lag(module, blade)
elif exists and state == "present":
update_lag(module, blade)
elif exists and state == "absent":
if module.params["name"].lower() == "uplink":
module.fail_json(
msg="Preconfigured Link Aggregation Group cannot be deleted"
)
else:
delete_lag(module, blade)
module.exit_json(changed=False)
if __name__ == "__main__":
main()
|