1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Persistence connection for cnos_vlan (#42500)

* Changing Lenovo Inc to Lenovo and update License file to be consistent.

* Changing cnos_vlan from paramiko to persistence connection of Ansible. Also talking care of CLI changes in CNOS commands with backward compatibility.

* Fixing Validation issues

* Trailing lines removal

* Review comments of Gundalow are getting addressed. He mentioned only at one place for cnos.py. But I have covered the entire file.

* Changes to incorporate Review comments from Qalthos

* Removing configure terminal command from module code

* Aligning with change in run_cnos_commands method changes

* Editing cliconf for latest CNOS CLIs
This commit is contained in:
Anil Kumar Muraleedharan 2018-07-18 21:47:08 +05:30 committed by Nathaniel Case
parent 1a0330488f
commit 0897e79bd1
6 changed files with 204 additions and 151 deletions

View file

@ -162,6 +162,32 @@ def run_commands(module, commands, check_rc=True):
return responses
def run_cnos_commands(module, commands, check_rc=True):
retVal = ''
enter_config = {'command': 'configure terminal', 'prompt': None, 'answer': None}
exit_config = {'command': 'end', 'prompt': None, 'answer': None}
commands.insert(0, enter_config)
commands.append(exit_config)
for cmd in commands:
retVal = retVal + '>> ' + cmd['command'] + '\n'
try:
responses = run_commands(module, commands, check_rc)
for response in responses:
retVal = retVal + '<< ' + response + '\n'
except Exception as e:
errMsg = ''
if hasattr(e, 'message'):
errMsg = e.message
else:
errMsg = str(e)
# Exception in Exceptions
if 'VLAN_ACCESS_MAP' in errMsg:
return retVal + '<<' + errMsg + '\n'
# Add more here if required
retVal = retVal + '<< ' + 'Error-101 ' + errMsg + '\n'
return str(retVal)
def load_config(module, config):
try:
conn = get_connection(module)
@ -2187,29 +2213,29 @@ def bgpConfig(
# EOM
def vlanConfig(
obj, deviceType, prompt, timeout, vlanArg1, vlanArg2, vlanArg3,
vlanArg4, vlanArg5):
def vlanConfig(module, prompt, answer):
retVal = ""
retVal = ''
# Wait time to get response from server
timeout = timeout
vlanArg1 = module.params['vlanArg1']
vlanArg2 = module.params['vlanArg2']
vlanArg3 = module.params['vlanArg3']
vlanArg4 = module.params['vlanArg4']
vlanArg5 = module.params['vlanArg5']
deviceType = module.params['deviceType']
# vlan config command happens here.
command = "vlan "
command = 'vlan '
if(vlanArg1 == "access-map"):
# debugOutput("access-map ")
command = command + vlanArg1 + " "
command = command + vlanArg1 + ' '
value = checkSanityofVariable(
deviceType, "vlan_access_map_name", vlanArg2)
if(value == "ok"):
command = command + vlanArg2 + " \n"
command = command + vlanArg2
# debugOutput(command)
retVal = waitForDeviceResponse(
command, "(config-access-map)#", timeout, obj)
retVal = retVal + vlanAccessMapConfig(
obj, deviceType, "(config-access-map)#", timeout, vlanArg3,
vlanArg4, vlanArg5)
cmd = [{'command': command, 'prompt': None, 'answer': None}]
retVal = retVal + vlanAccessMapConfig(module, cmd)
return retVal
else:
retVal = "Error-130"
@ -2244,7 +2270,7 @@ def vlanConfig(
if(value == "ok"):
command = command + vlanArg3
else:
retVal = "ERROR-133"
retVal = "Error-133"
return retVal
else:
retVal = "Error-132"
@ -2253,37 +2279,34 @@ def vlanConfig(
else:
value = checkSanityofVariable(deviceType, "vlan_id", vlanArg1)
if(value == "ok"):
retVal = createVlan(obj, deviceType, "(config-vlan)#",
timeout, vlanArg1, vlanArg2, vlanArg3,
vlanArg4, vlanArg5)
retVal = createVlan(module, '(config-vlan)#', None)
return retVal
else:
value = checkSanityofVariable(
deviceType, "vlan_id_range", vlanArg1)
if(value == "ok"):
retVal = createVlan(obj, deviceType, "(config-vlan)#",
timeout, vlanArg1, vlanArg2, vlanArg3,
vlanArg4, vlanArg5)
retVal = createVlan(module, '(config-vlan)#', None)
return retVal
retVal = "Error-133"
return retVal
# debugOutput(command)
command = command + "\n"
# debugOutput(command)
retVal = retVal + waitForDeviceResponse(command, prompt, timeout, obj)
cmd = [{'command': command, 'prompt': None, 'answer': None}]
retVal = retVal + str(run_cnos_commands(module, cmd))
return retVal
# EOM
def vlanAccessMapConfig(
obj, deviceType, prompt, timeout, vlanArg3, vlanArg4, vlanArg5):
retVal = ""
def vlanAccessMapConfig(module, cmd):
retVal = ''
# Wait time to get response from server
timeout = timeout
command = ""
command = ''
vlanArg3 = module.params['vlanArg3']
vlanArg4 = module.params['vlanArg4']
vlanArg5 = module.params['vlanArg5']
deviceType = module.params['deviceType']
if(vlanArg3 == "action"):
command = command + vlanArg3 + " "
command = command + vlanArg3 + ' '
value = checkSanityofVariable(
deviceType, "vlan_accessmap_action", vlanArg4)
if(value == "ok"):
@ -2292,9 +2315,9 @@ def vlanAccessMapConfig(
retVal = "Error-135"
return retVal
elif(vlanArg3 == "match"):
command = command + vlanArg3 + " "
command = command + vlanArg3 + ' '
if(vlanArg4 == "ip" or vlanArg4 == "mac"):
command = command + vlanArg4 + " address "
command = command + vlanArg4 + ' address '
value = checkSanityofVariable(
deviceType, "vlan_access_map_name", vlanArg5)
if(value == "ok"):
@ -2311,18 +2334,24 @@ def vlanAccessMapConfig(
retVal = "Error-138"
return retVal
command = command + "\n"
inner_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(inner_cmd)
retVal = retVal + str(run_cnos_commands(module, cmd))
# debugOutput(command)
retVal = retVal + waitForDeviceResponse(command, prompt, timeout, obj)
return retVal
# EOM
def checkVlanNameNotAssigned(
obj, deviceType, prompt, timeout, vlanId, vlanName):
def checkVlanNameNotAssigned(module, prompt, answer):
retVal = "ok"
command = "display vlan id " + vlanId + " \n"
retVal = waitForDeviceResponse(command, prompt, timeout, obj)
vlanId = module.params['vlanArg1']
vlanName = module.params['vlanArg3']
command = "show vlan id " + vlanId
cmd = [{'command': command, 'prompt': None, 'answer': None}]
retVal = str(run_cnos_commands(module, cmd))
if(retVal.find('Error') != -1):
command = "display vlan id " + vlanId
retVal = str(run_cnos_commands(module, cmd))
if(retVal.find(vlanName) != -1):
return "Nok"
else:
@ -2331,25 +2360,30 @@ def checkVlanNameNotAssigned(
# Utility Method to create vlan
def createVlan(
obj, deviceType, prompt, timeout, vlanArg1, vlanArg2, vlanArg3,
vlanArg4, vlanArg5):
def createVlan(module, prompt, answer):
# vlan config command happens here. It creates if not present
command = "vlan " + vlanArg1 + "\n"
vlanArg1 = module.params['vlanArg1']
vlanArg2 = module.params['vlanArg2']
vlanArg3 = module.params['vlanArg3']
vlanArg4 = module.params['vlanArg4']
vlanArg5 = module.params['vlanArg5']
deviceType = module.params['deviceType']
retVal = ''
command = 'vlan ' + vlanArg1
# debugOutput(command)
retVal = waitForDeviceResponse(command, prompt, timeout, obj)
cmd = [{'command': command, 'prompt': None, 'answer': None}]
command = ""
if(vlanArg2 == "name"):
# debugOutput("name")
command = vlanArg2 + " "
value = checkSanityofVariable(deviceType, "vlan_name", vlanArg3)
if(value == "ok"):
value = checkVlanNameNotAssigned(obj, deviceType, prompt, timeout,
vlanArg1, vlanArg3)
value = checkVlanNameNotAssigned(module, prompt, answer)
if(value == "ok"):
command = command + vlanArg3
else:
retVal = retVal + 'VLAN Name is already assigned \n'
command = "\n"
else:
retVal = "Error-139"
@ -2470,13 +2504,6 @@ def createVlan(
retVal = "Error-149"
return retVal
elif (vlanArg3 == "static-group"):
# debugOutput("static-group")
# command = command + vlanArg3 + " "
# value = checkSanityofVariable(deviceType, variableId, vlanArg4)
# if(value == "ok"):
# command = command + vlanArg4
# else :
retVal = "Error-102"
return retVal
elif (vlanArg3 == "version"):
@ -2519,15 +2546,10 @@ def createVlan(
else:
retVal = "Error-154"
return retVal
command = command + "\n"
inner_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(inner_cmd)
retVal = retVal + str(run_cnos_commands(module, cmd))
# debugOutput(command)
retVal = retVal + "\n" + \
waitForDeviceResponse(command, prompt, timeout, obj)
# Come back to config mode
command = "exit \n"
# debugOutput(command)
retVal = retVal + waitForDeviceResponse(command, "(config)#", timeout, obj)
return retVal
# EOM
@ -2771,7 +2793,7 @@ def doSecureStartupConfigBackUp(
username + "@" + server + "/" + confPath + " vrf management\n"
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
command = password + "\n"
retVal = retVal + waitForDeviceResponse(command, "#", timeout, obj)
return retVal
@ -2874,7 +2896,7 @@ def doSecureStartUpConfigRollback(
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
command = password + "\n"
retVal = retVal + waitForDeviceResponse(command, "[n]", timeout, obj)
command = "y\n"
@ -2972,7 +2994,7 @@ def doSecureRunningConfigBackUp(
username + "@" + server + "/" + confPath + " vrf management\n"
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
command = password + "\n"
retVal = retVal + waitForDeviceResponse(command, "#", timeout, obj)
return retVal
@ -3071,7 +3093,7 @@ def doSecureRunningConfigRollback(
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
command = password + "\n"
retVal = retVal + waitForDeviceResponse(command, "#", timeout, obj)
return retVal
@ -3135,7 +3157,7 @@ def doImageTransfer(
return "Error-110"
# debugOutput(command)
response = waitForDeviceResponse(command, "[n]", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
retVal = retVal
else:
retVal = retVal + response
@ -3185,7 +3207,7 @@ def doSecureImageTransfer(
server + "/" + imgPath + " system-image " + type + " vrf management \n"
# debugOutput(command)
response = waitForDeviceResponse(command, "[n]", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
retVal = retVal
else:
retVal = retVal + response
@ -3194,7 +3216,7 @@ def doSecureImageTransfer(
command = "y\n"
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)?", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
retVal = retVal
else:
retVal = retVal + response
@ -3208,7 +3230,7 @@ def doSecureImageTransfer(
command = "y\n"
# debugOutput(command)
response = waitForDeviceResponse(command, "(yes/no)?", 3, obj)
if(response.lower().find("error-101")):
if(response.lower().find('error-101')):
retVal = retVal
else:
retVal = retVal + response
@ -3320,19 +3342,19 @@ def waitForDeviceResponse(command, prompt, timeout, obj):
def checkOutputForError(output):
retVal = ""
index = output.lower().find("error")
index = output.lower().find('error')
startIndex = index + 6
if(index == -1):
index = output.lower().find("invalid")
index = output.lower().find('invalid')
startIndex = index + 8
if(index == -1):
index = output.lower().find("cannot be enabled in l2 interface")
index = output.lower().find('cannot be enabled in l2 interface')
startIndex = index + 34
if(index == -1):
index = output.lower().find("incorrect")
index = output.lower().find('incorrect')
startIndex = index + 10
if(index == -1):
index = output.lower().find("failure")
index = output.lower().find('failure')
startIndex = index + 8
if(index == -1):
return None

View file

@ -43,13 +43,12 @@ description:
filter. After passing this level, there are five VLAN arguments that will
perform further configurations. They are vlanArg1, vlanArg2, vlanArg3,
vlanArg4, and vlanArg5. The value of vlanArg1 will determine the way
following arguments will be evaluated. For more details on how to use these
arguments, see [Overloaded Variables].
This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the playbook is run.
For more information about this module from Lenovo and customizing it usage for your
use cases, please visit U(http://systemx.lenovofiles.com/help/index.jsp?topic=%2Fcom.lenovo.switchmgt.ansible.doc%2Fcnos_vlan.html)
following arguments will be evaluated. This module uses SSH to manage network
device configuration. The results of the operation will be placed in a directory
named 'results' that must be created by the user in their local directory to
where the playbook is run. For more information about this module from Lenovo and
customizing it usage for your use cases,
please visit U(http://systemx.lenovofiles.com/help/index.jsp?topic=%2Fcom.lenovo.switchmgt.ansible.doc%2Fcnos_vlan.html)
version_added: "2.3"
extends_documentation_fragment: cnos
options:
@ -182,24 +181,17 @@ msg:
'''
import sys
try:
import paramiko
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
import time
import socket
import array
import json
import time
import re
try:
from ansible.module_utils.network.cnos import cnos
HAS_LIB = True
except:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
@ -223,58 +215,14 @@ def main():
vlanArg5=dict(required=False),),
supports_check_mode=False)
username = module.params['username']
password = module.params['password']
enablePassword = module.params['enablePassword']
vlanArg1 = module.params['vlanArg1']
vlanArg2 = module.params['vlanArg2']
vlanArg3 = module.params['vlanArg3']
vlanArg4 = module.params['vlanArg4']
vlanArg5 = module.params['vlanArg5']
outputfile = module.params['outputfile']
hostIP = module.params['host']
deviceType = module.params['deviceType']
output = ""
if not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required for this module')
# Create instance of SSHClient object
remote_conn_pre = paramiko.SSHClient()
# Automatically add untrusted hosts (make sure okay for security policy in
# your environment)
remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# initiate SSH connection with the switch
remote_conn_pre.connect(hostIP, username=username, password=password)
time.sleep(2)
# Use invoke_shell to establish an 'interactive session'
remote_conn = remote_conn_pre.invoke_shell()
time.sleep(2)
# Enable and enter configure terminal then send command
output = output + cnos.waitForDeviceResponse("\n", ">", 2, remote_conn)
output = output + \
cnos.enterEnableModeForDevice(enablePassword, 3, remote_conn)
# Make terminal length = 0
output = output + \
cnos.waitForDeviceResponse("terminal length 0\n", "#", 2, remote_conn)
# Go to config mode
output = output + \
cnos.waitForDeviceResponse("configure device\n", "(config)#", 2, remote_conn)
# Send the CLi command
output = output + \
cnos.vlanConfig(
remote_conn, deviceType, "(config)#", 2, vlanArg1, vlanArg2,
vlanArg3, vlanArg4, vlanArg5)
output = output + str(cnos.vlanConfig(module, "(config)#", None))
# Save it into the file
# Save it operation details into the file
file = open(outputfile, "a")
file.write(output)
file.close()
@ -282,7 +230,8 @@ def main():
# need to add logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="VLAN configuration is accomplished")
module.exit_json(changed=True,
msg="VLAN configuration is accomplished")
else:
module.fail_json(msg=errorMsg)

View file

@ -12,6 +12,7 @@
# GNU General Public License for more details.
#
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Module to Collect facts from Lenovo Switches running Lenovo ENOS commands
# Lenovo Networking
#

View file

@ -32,39 +32,52 @@ class Cliconf(CliconfBase):
device_info = {}
device_info['network_os'] = 'cnos'
reply = self.get(b'display version')
reply = self.get(b'show sys-info')
data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'^System version: (.*?) ', data, re.M | re.I)
if match:
device_info['network_os_version'] = match.group(1)
match = re.search(r'^Lenovo RackSwitch (\S+)', data, re.M | re.I)
if match:
device_info['network_os_model'] = match.group(1)
match = re.search(r'^Device name: (.*?) ', data, re.M | re.I)
if match:
device_info['network_os_hostname'] = match.group(1)
else:
device_info['network_os_hostname'] = "NA"
host = self.get(b'show hostname')
hostname = to_text(host, errors='surrogate_or_strict').strip()
if data:
device_info['network_os_version'] = self.parse_version(data)
device_info['network_os_model'] = self.parse_model(data)
device_info['network_os_hostname'] = hostname
return device_info
def parse_version(self, data):
for line in data.split('\n'):
line = line.strip()
match = re.match(r'System Software Revision (.*?)',
line, re.M | re.I)
if match:
vers = line.split(':')
ver = vers[1].strip()
return ver
return "NA"
def parse_model(self, data):
for line in data.split('\n'):
line = line.strip()
match = re.match(r'System Model (.*?)', line, re.M | re.I)
if match:
mdls = line.split(':')
mdl = mdls[1].strip()
return mdl
return "NA"
@enable_mode
def get_config(self, source='running', format='text'):
if source not in ('running', 'startup'):
msg = "fetching configuration from %s is not supported"
return self.invalid_params(msg % source)
if source == 'running':
cmd = b'display running-config'
cmd = b'show running-config'
else:
cmd = b'display startup-config'
cmd = b'show startup-config'
return self.send_command(cmd)
@enable_mode
def edit_config(self, command):
for cmd in chain([b'configure device'], to_list(command), [b'end']):
for cmd in chain([b'configure terminal'], to_list(command), [b'end']):
self.send_command(cmd)
def get(self, command, prompt=None, answer=None, sendonly=False):

View file

@ -0,0 +1,21 @@
VLAN Name Status IPMC FLOOD Ports
======== ================================ ======= ========== ===================
1 default ACTIVE IPv6
Ethernet1/2(u)
Ethernet1/3(t)
Ethernet1/4(t)
Ethernet1/9(u)
Ethernet1/10(u)
Ethernet1/14(u)
Ethernet1/15(u)
Ethernet1/16(u)
Ethernet1/17(u)
13 anil ACTIVE IPv4,IPv6
po13(t)
po17(t)
po100(t)
po1003(t)
po1004(t)
Ethernet1/3(t)
Ethernet1/4(t)

View file

@ -0,0 +1,47 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests.mock import patch
from ansible.modules.network.cnos import cnos_vlan
from units.modules.utils import set_module_args
from .cnos_module import TestCnosModule, load_fixture
class TestCnosVlanModule(TestCnosModule):
module = cnos_vlan
def setUp(self):
super(TestCnosVlanModule, self).setUp()
self.mock_run_cnos_commands = patch('ansible.module_utils.network.cnos.cnos.run_cnos_commands')
self.run_cnos_commands = self.mock_run_cnos_commands.start()
def tearDown(self):
super(TestCnosVlanModule, self).tearDown()
self.mock_run_cnos_commands.stop()
def load_fixtures(self, commands=None, transport='cli'):
self.run_cnos_commands.return_value = [load_fixture('cnos_vlan_config.cfg')]
def test_cnos_vlan_create(self):
set_module_args({'username': 'admin', 'password': 'pass',
'host': '10.241.107.39', 'deviceType': 'g8272_cnos',
'outputfile': 'test.log', 'vlanArg1': '13',
'vlanArg2': 'name', 'vlanArg3': 'anil'})
result = self.execute_module(changed=True)
file = open('Anil.txt', "a")
file.write(str(result))
file.close()
expected_result = 'VLAN configuration is accomplished'
self.assertEqual(result['msg'], expected_result)
def test_cnos_vlan_state(self):
set_module_args({'username': 'admin', 'password': 'pass',
'host': '10.241.107.39', 'deviceType': 'g8272_cnos',
'outputfile': 'test.log', 'vlanArg1': '13',
'vlanArg2': 'state', 'vlanArg3': 'active'})
result = self.execute_module(changed=True)
expected_result = 'VLAN configuration is accomplished'
self.assertEqual(result['msg'], expected_result)