386 lines
11 KiB
Python
386 lines
11 KiB
Python
#!/usr/bin/python
|
|
# Copyright: Ansible Project
|
|
# 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
|
|
|
|
|
|
DOCUMENTATION = """
|
|
module: nxos_vsan
|
|
short_description: Configuration of vsan for Cisco NXOS MDS Switches.
|
|
description:
|
|
- Configuration of vsan for Cisco MDS NXOS.
|
|
version_added: 1.0.0
|
|
author:
|
|
- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
|
|
notes:
|
|
- Tested against Cisco MDS NX-OS 8.4(1)
|
|
options:
|
|
vsan:
|
|
description:
|
|
- List of vsan details to be added or removed
|
|
type: list
|
|
elements: dict
|
|
suboptions:
|
|
id:
|
|
description:
|
|
- Vsan id
|
|
required: true
|
|
type: int
|
|
name:
|
|
description:
|
|
- Name of the vsan
|
|
type: str
|
|
suspend:
|
|
description:
|
|
- suspend the vsan if True
|
|
type: bool
|
|
remove:
|
|
description:
|
|
- Removes the vsan if True
|
|
type: bool
|
|
interface:
|
|
description:
|
|
- List of vsan's interfaces to be added
|
|
type: list
|
|
elements: str
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
- name: Test that vsan module works
|
|
cisco.nxos.nxos_vsan:
|
|
vsan:
|
|
- id: 922
|
|
interface:
|
|
- fc1/1
|
|
- fc1/2
|
|
- port-channel 1
|
|
name: vsan-SAN-A
|
|
remove: false
|
|
suspend: false
|
|
- id: 923
|
|
interface:
|
|
- fc1/11
|
|
- fc1/21
|
|
- port-channel 2
|
|
name: vsan-SAN-B
|
|
remove: false
|
|
suspend: true
|
|
- id: 1923
|
|
name: vsan-SAN-Old
|
|
remove: true
|
|
"""
|
|
|
|
RETURN = """
|
|
commands:
|
|
description: commands sent to the device
|
|
returned: always
|
|
type: list
|
|
sample:
|
|
- terminal dont-ask
|
|
- vsan database
|
|
- vsan 922 interface fc1/40
|
|
- vsan 922 interface port-channel 155
|
|
- no terminal dont-ask
|
|
"""
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
|
|
load_config,
|
|
run_commands,
|
|
)
|
|
import re
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
class Vsan(object):
|
|
def __init__(self, vsanid):
|
|
self.vsanid = vsanid
|
|
self.vsanname = None
|
|
self.vsanstate = None
|
|
self.vsanoperstate = None
|
|
self.vsaninterfaces = []
|
|
|
|
|
|
class GetVsanInfoFromSwitch(object):
|
|
"""docstring for GetVsanInfoFromSwitch"""
|
|
|
|
def __init__(self, module):
|
|
self.module = module
|
|
self.vsaninfo = {}
|
|
self.processShowVsan()
|
|
self.processShowVsanMembership()
|
|
|
|
def execute_show_vsan_cmd(self):
|
|
output = execute_show_command("show vsan", self.module)[0]
|
|
return output
|
|
|
|
def execute_show_vsan_mem_cmd(self):
|
|
output = execute_show_command("show vsan membership", self.module)[0]
|
|
return output
|
|
|
|
def processShowVsan(self):
|
|
patv = r"^vsan\s+(\d+)\s+information"
|
|
patnamestate = "name:(.*)state:(.*)"
|
|
patoperstate = "operational state:(.*)"
|
|
|
|
output = self.execute_show_vsan_cmd().split("\n")
|
|
for o in output:
|
|
z = re.match(patv, o.strip())
|
|
if z:
|
|
v = z.group(1).strip()
|
|
self.vsaninfo[v] = Vsan(v)
|
|
|
|
z1 = re.match(patnamestate, o.strip())
|
|
if z1:
|
|
n = z1.group(1).strip()
|
|
s = z1.group(2).strip()
|
|
self.vsaninfo[v].vsanname = n
|
|
self.vsaninfo[v].vsanstate = s
|
|
|
|
z2 = re.match(patoperstate, o.strip())
|
|
if z2:
|
|
oper = z2.group(1).strip()
|
|
self.vsaninfo[v].vsanoperstate = oper
|
|
|
|
# 4094/4079 vsan is always present
|
|
self.vsaninfo["4079"] = Vsan("4079")
|
|
self.vsaninfo["4094"] = Vsan("4094")
|
|
|
|
def processShowVsanMembership(self):
|
|
patv = r"^vsan\s+(\d+).*"
|
|
output = self.execute_show_vsan_mem_cmd().split("\n")
|
|
memlist = []
|
|
v = None
|
|
for o in output:
|
|
z = re.match(patv, o.strip())
|
|
if z:
|
|
if v is not None:
|
|
self.vsaninfo[v].vsaninterfaces = memlist
|
|
memlist = []
|
|
v = z.group(1)
|
|
if "interfaces" not in o:
|
|
llist = o.strip().split()
|
|
memlist = memlist + llist
|
|
self.vsaninfo[v].vsaninterfaces = memlist
|
|
|
|
def getVsanInfoObjects(self):
|
|
return self.vsaninfo
|
|
|
|
|
|
def execute_show_command(command, module, command_type="cli_show"):
|
|
output = "text"
|
|
commands = [{"command": command, "output": output}]
|
|
return run_commands(module, commands)
|
|
|
|
|
|
def flatten_list(command_lists):
|
|
flat_command_list = []
|
|
for command in command_lists:
|
|
if isinstance(command, list):
|
|
flat_command_list.extend(command)
|
|
else:
|
|
flat_command_list.append(command)
|
|
return flat_command_list
|
|
|
|
|
|
def main():
|
|
vsan_element_spec = dict(
|
|
id=dict(required=True, type="int"),
|
|
name=dict(type="str"),
|
|
remove=dict(type="bool"),
|
|
suspend=dict(type="bool"),
|
|
interface=dict(type="list", elements="str"),
|
|
)
|
|
|
|
argument_spec = dict(
|
|
vsan=dict(type="list", elements="dict", options=vsan_element_spec)
|
|
)
|
|
|
|
module = AnsibleModule(
|
|
argument_spec=argument_spec, supports_check_mode=True
|
|
)
|
|
warnings = list()
|
|
messages = list()
|
|
commands_executed = list()
|
|
result = {"changed": False}
|
|
|
|
obj = GetVsanInfoFromSwitch(module)
|
|
dictSwVsanObjs = obj.getVsanInfoObjects()
|
|
|
|
commands = []
|
|
vsan_list = module.params["vsan"]
|
|
|
|
for eachvsan in vsan_list:
|
|
vsanid = str(eachvsan["id"])
|
|
vsanname = eachvsan["name"]
|
|
vsanremove = eachvsan["remove"]
|
|
vsansuspend = eachvsan["suspend"]
|
|
vsaninterface_list = eachvsan["interface"]
|
|
|
|
if int(vsanid) < 1 or int(vsanid) >= 4095:
|
|
module.fail_json(
|
|
msg=vsanid
|
|
+ " - This is an invalid vsan. Supported vsan range is 1-4094"
|
|
)
|
|
|
|
if vsanid in dictSwVsanObjs.keys():
|
|
sw_vsanid = vsanid
|
|
sw_vsanname = dictSwVsanObjs[vsanid].vsanname
|
|
sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate
|
|
sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces
|
|
else:
|
|
sw_vsanid = None
|
|
sw_vsanname = None
|
|
sw_vsanstate = None
|
|
sw_vsaninterfaces = []
|
|
|
|
if vsanremove:
|
|
# Negative case:
|
|
if vsanid == "4079" or vsanid == "4094":
|
|
messages.append(
|
|
str(vsanid)
|
|
+ " is a reserved vsan, hence cannot be removed"
|
|
)
|
|
continue
|
|
if vsanid == sw_vsanid:
|
|
commands.append("no vsan " + str(vsanid))
|
|
messages.append("deleting the vsan " + str(vsanid))
|
|
else:
|
|
messages.append(
|
|
"There is no vsan "
|
|
+ str(vsanid)
|
|
+ " present in the switch. Hence there is nothing to delete"
|
|
)
|
|
continue
|
|
else:
|
|
# Negative case:
|
|
if vsanid == "4079" or vsanid == "4094":
|
|
messages.append(
|
|
str(vsanid)
|
|
+ " is a reserved vsan, and always present on the switch"
|
|
)
|
|
else:
|
|
if vsanid == sw_vsanid:
|
|
messages.append(
|
|
"There is already a vsan "
|
|
+ str(vsanid)
|
|
+ " present in the switch. Hence there is nothing to configure"
|
|
)
|
|
else:
|
|
commands.append("vsan " + str(vsanid))
|
|
messages.append("creating vsan " + str(vsanid))
|
|
|
|
if vsanname is not None:
|
|
# Negative case:
|
|
if vsanid == "4079" or vsanid == "4094":
|
|
messages.append(
|
|
str(vsanid) + " is a reserved vsan, and cannot be renamed"
|
|
)
|
|
else:
|
|
if vsanname == sw_vsanname:
|
|
messages.append(
|
|
"There is already a vsan "
|
|
+ str(vsanid)
|
|
+ " present in the switch, which has the name "
|
|
+ vsanname
|
|
+ " Hence there is nothing to configure"
|
|
)
|
|
else:
|
|
commands.append(
|
|
"vsan " + str(vsanid) + " name " + vsanname
|
|
)
|
|
messages.append(
|
|
"setting vsan name to "
|
|
+ vsanname
|
|
+ " for vsan "
|
|
+ str(vsanid)
|
|
)
|
|
|
|
if vsansuspend:
|
|
# Negative case:
|
|
if vsanid == "4079" or vsanid == "4094":
|
|
messages.append(
|
|
str(vsanid)
|
|
+ " is a reserved vsan, and cannot be suspended"
|
|
)
|
|
else:
|
|
if sw_vsanstate == "suspended":
|
|
messages.append(
|
|
"There is already a vsan "
|
|
+ str(vsanid)
|
|
+ " present in the switch, which is in suspended state "
|
|
)
|
|
else:
|
|
commands.append("vsan " + str(vsanid) + " suspend")
|
|
messages.append("suspending the vsan " + str(vsanid))
|
|
else:
|
|
if sw_vsanstate == "active":
|
|
messages.append(
|
|
"There is already a vsan "
|
|
+ str(vsanid)
|
|
+ " present in the switch, which is in active state "
|
|
)
|
|
else:
|
|
commands.append("no vsan " + str(vsanid) + " suspend")
|
|
messages.append("no suspending the vsan " + str(vsanid))
|
|
|
|
if vsaninterface_list is not None:
|
|
for each_interface_name in vsaninterface_list:
|
|
# For fcip,port-channel,vfc-port-channel need to remove the
|
|
# extra space to compare
|
|
temp = re.sub(" +", "", each_interface_name)
|
|
if temp in sw_vsaninterfaces:
|
|
messages.append(
|
|
each_interface_name
|
|
+ " is already present in the vsan "
|
|
+ str(vsanid)
|
|
+ " interface list"
|
|
)
|
|
else:
|
|
commands.append(
|
|
"vsan "
|
|
+ str(vsanid)
|
|
+ " interface "
|
|
+ each_interface_name
|
|
)
|
|
messages.append(
|
|
"adding interface "
|
|
+ each_interface_name
|
|
+ " to vsan "
|
|
+ str(vsanid)
|
|
)
|
|
|
|
if len(commands) != 0:
|
|
commands = (
|
|
["terminal dont-ask"]
|
|
+ ["vsan database"]
|
|
+ commands
|
|
+ ["no terminal dont-ask"]
|
|
)
|
|
|
|
cmds = flatten_list(commands)
|
|
commands_executed = cmds
|
|
|
|
if commands_executed:
|
|
if module.check_mode:
|
|
module.exit_json(
|
|
changed=False,
|
|
commands=commands_executed,
|
|
msg="Check Mode: No cmds issued to the hosts",
|
|
)
|
|
else:
|
|
result["changed"] = True
|
|
load_config(module, commands_executed)
|
|
|
|
result["messages"] = messages
|
|
result["commands"] = commands_executed
|
|
result["warnings"] = warnings
|
|
module.exit_json(**result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|