mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
660 lines
20 KiB
Python
660 lines
20 KiB
Python
# This code is part of Ansible, but is an independent component.
|
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
# Modules you write using this snippet, which is embedded dynamically by
|
|
# Ansible still belong to the author of the module, and may assign their own
|
|
# license to the complete work.
|
|
#
|
|
# Copyright (C) 2017 Lenovo, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
# Contains utility methods
|
|
# Lenovo Networking
|
|
|
|
import time
|
|
import socket
|
|
import re
|
|
import json
|
|
try:
|
|
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos_errorcodes
|
|
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos_devicerules
|
|
HAS_LIB = True
|
|
except Exception:
|
|
HAS_LIB = False
|
|
from distutils.cmd import Command
|
|
from ansible.module_utils._text import to_text
|
|
from ansible.module_utils.basic import env_fallback
|
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection
|
|
from ansible.module_utils.connection import Connection, exec_command
|
|
from ansible.module_utils.connection import ConnectionError
|
|
|
|
_DEVICE_CONFIGS = {}
|
|
_CONNECTION = None
|
|
_VALID_USER_ROLES = ['network-admin', 'network-operator']
|
|
|
|
cnos_provider_spec = {
|
|
'host': dict(),
|
|
'port': dict(type='int'),
|
|
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
|
|
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']),
|
|
no_log=True),
|
|
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']),
|
|
type='path'),
|
|
'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']),
|
|
type='bool'),
|
|
'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']),
|
|
no_log=True),
|
|
'timeout': dict(type='int'),
|
|
'context': dict(),
|
|
'passwords': dict()
|
|
}
|
|
|
|
cnos_argument_spec = {
|
|
'provider': dict(type='dict', options=cnos_provider_spec),
|
|
}
|
|
|
|
command_spec = {
|
|
'command': dict(key=True),
|
|
'prompt': dict(),
|
|
'answer': dict(),
|
|
'check_all': dict()
|
|
}
|
|
|
|
|
|
def get_provider_argspec():
|
|
return cnos_provider_spec
|
|
|
|
|
|
def check_args(module, warnings):
|
|
pass
|
|
|
|
|
|
def get_user_roles():
|
|
return _VALID_USER_ROLES
|
|
|
|
|
|
def get_connection(module):
|
|
global _CONNECTION
|
|
if _CONNECTION:
|
|
return _CONNECTION
|
|
_CONNECTION = Connection(module._socket_path)
|
|
|
|
context = None
|
|
try:
|
|
context = module.params['context']
|
|
except KeyError:
|
|
context = None
|
|
|
|
if context:
|
|
if context == 'system':
|
|
command = 'changeto system'
|
|
else:
|
|
command = 'changeto context %s' % context
|
|
_CONNECTION.get(command)
|
|
|
|
return _CONNECTION
|
|
|
|
|
|
def get_config(module, flags=None):
|
|
flags = [] if flags is None else flags
|
|
|
|
passwords = None
|
|
try:
|
|
passwords = module.params['passwords']
|
|
except KeyError:
|
|
passwords = None
|
|
if passwords:
|
|
cmd = 'more system:running-config'
|
|
else:
|
|
cmd = 'display running-config '
|
|
cmd += ' '.join(flags)
|
|
cmd = cmd.strip()
|
|
|
|
try:
|
|
return _DEVICE_CONFIGS[cmd]
|
|
except KeyError:
|
|
conn = get_connection(module)
|
|
out = conn.get(cmd)
|
|
cfg = to_text(out, errors='surrogate_then_replace').strip()
|
|
_DEVICE_CONFIGS[cmd] = cfg
|
|
return cfg
|
|
|
|
|
|
def to_commands(module, commands):
|
|
if not isinstance(commands, list):
|
|
raise AssertionError('argument must be of type <list>')
|
|
|
|
transform = EntityCollection(module, command_spec)
|
|
commands = transform(commands)
|
|
|
|
for index, item in enumerate(commands):
|
|
if module.check_mode and not item['command'].startswith('show'):
|
|
module.warn('only show commands are supported when using check '
|
|
'mode, not executing `%s`' % item['command'])
|
|
|
|
return commands
|
|
|
|
|
|
def run_commands(module, commands, check_rc=True):
|
|
connection = get_connection(module)
|
|
connection.get('enable')
|
|
commands = to_commands(module, to_list(commands))
|
|
|
|
responses = list()
|
|
|
|
for cmd in commands:
|
|
out = connection.get(**cmd)
|
|
responses.append(to_text(out, errors='surrogate_then_replace'))
|
|
|
|
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'
|
|
if 'confederation identifier' in errMsg:
|
|
return retVal + '<<' + errMsg + '\n'
|
|
# Add more here if required
|
|
retVal = retVal + '<< ' + 'Error-101 ' + errMsg + '\n'
|
|
return str(retVal)
|
|
|
|
|
|
def get_capabilities(module):
|
|
if hasattr(module, '_cnos_capabilities'):
|
|
return module._cnos_capabilities
|
|
try:
|
|
capabilities = Connection(module._socket_path).get_capabilities()
|
|
except ConnectionError as exc:
|
|
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
module._cnos_capabilities = json.loads(capabilities)
|
|
return module._cnos_capabilities
|
|
|
|
|
|
def load_config(module, config):
|
|
try:
|
|
conn = get_connection(module)
|
|
conn.get('enable')
|
|
resp = conn.edit_config(config)
|
|
return resp.get('response')
|
|
except ConnectionError as exc:
|
|
module.fail_json(msg=to_text(exc))
|
|
|
|
|
|
def get_defaults_flag(module):
|
|
rc, out, err = exec_command(module, 'display running-config ?')
|
|
out = to_text(out, errors='surrogate_then_replace')
|
|
|
|
commands = set()
|
|
for line in out.splitlines():
|
|
if line:
|
|
commands.add(line.strip().split()[0])
|
|
|
|
if 'all' in commands:
|
|
return 'all'
|
|
else:
|
|
return 'full'
|
|
|
|
|
|
def enterEnableModeForDevice(enablePassword, timeout, obj):
|
|
command = "enable\n"
|
|
pwdPrompt = "password:"
|
|
# debugOutput(enablePassword)
|
|
# debugOutput('\n')
|
|
obj.settimeout(int(timeout))
|
|
# Executing enable
|
|
obj.send(command)
|
|
flag = False
|
|
retVal = ""
|
|
count = 5
|
|
while not flag:
|
|
# If wait time is execeeded.
|
|
if(count == 0):
|
|
flag = True
|
|
else:
|
|
count = count - 1
|
|
# A delay of one second
|
|
time.sleep(1)
|
|
try:
|
|
buffByte = obj.recv(9999)
|
|
buff = buffByte.decode()
|
|
retVal = retVal + buff
|
|
# debugOutput(buff)
|
|
gotit = buff.find(pwdPrompt)
|
|
if(gotit != -1):
|
|
time.sleep(1)
|
|
if(enablePassword is None or enablePassword == ""):
|
|
return "\n Error-106"
|
|
obj.send(enablePassword)
|
|
obj.send("\r")
|
|
obj.send("\n")
|
|
time.sleep(1)
|
|
innerBuffByte = obj.recv(9999)
|
|
innerBuff = innerBuffByte.decode()
|
|
retVal = retVal + innerBuff
|
|
# debugOutput(innerBuff)
|
|
innerGotit = innerBuff.find("#")
|
|
if(innerGotit != -1):
|
|
return retVal
|
|
else:
|
|
gotit = buff.find("#")
|
|
if(gotit != -1):
|
|
return retVal
|
|
except Exception:
|
|
retVal = retVal + "\n Error-101"
|
|
flag = True
|
|
if(retVal == ""):
|
|
retVal = "\n Error-101"
|
|
return retVal
|
|
# EOM
|
|
|
|
|
|
def waitForDeviceResponse(command, prompt, timeout, obj):
|
|
obj.settimeout(int(timeout))
|
|
obj.send(command)
|
|
flag = False
|
|
retVal = ""
|
|
while not flag:
|
|
time.sleep(1)
|
|
try:
|
|
buffByte = obj.recv(9999)
|
|
buff = buffByte.decode()
|
|
retVal = retVal + buff
|
|
# debugOutput(retVal)
|
|
gotit = buff.find(prompt)
|
|
if(gotit != -1):
|
|
flag = True
|
|
except Exception:
|
|
# debugOutput(prompt)
|
|
if prompt == "(yes/no)?":
|
|
pass
|
|
elif prompt == "Password:":
|
|
pass
|
|
else:
|
|
retVal = retVal + "\n Error-101"
|
|
flag = True
|
|
return retVal
|
|
# EOM
|
|
|
|
|
|
def checkOutputForError(output):
|
|
retVal = ""
|
|
index = output.lower().find('error')
|
|
startIndex = index + 6
|
|
if(index == -1):
|
|
index = output.lower().find('invalid')
|
|
startIndex = index + 8
|
|
if(index == -1):
|
|
index = output.lower().find('cannot be enabled in l2 interface')
|
|
startIndex = index + 34
|
|
if(index == -1):
|
|
index = output.lower().find('incorrect')
|
|
startIndex = index + 10
|
|
if(index == -1):
|
|
index = output.lower().find('failure')
|
|
startIndex = index + 8
|
|
if(index == -1):
|
|
return None
|
|
|
|
endIndex = startIndex + 3
|
|
errorCode = output[startIndex:endIndex]
|
|
result = errorCode.isdigit()
|
|
if(result is not True):
|
|
return "Device returned an Error. Please check Results for more \
|
|
information"
|
|
|
|
errorFile = "dictionary/ErrorCodes.lvo"
|
|
try:
|
|
# with open(errorFile, 'r') as f:
|
|
f = open(errorFile, 'r')
|
|
for line in f:
|
|
if('=' in line):
|
|
data = line.split('=')
|
|
if(data[0].strip() == errorCode):
|
|
errorString = data[1].strip()
|
|
return errorString
|
|
except Exception:
|
|
errorString = cnos_errorcodes.getErrorString(errorCode)
|
|
errorString = errorString.strip()
|
|
return errorString
|
|
return "Error Code Not Found"
|
|
# EOM
|
|
|
|
|
|
def checkSanityofVariable(deviceType, variableId, variableValue):
|
|
retVal = ""
|
|
ruleFile = "dictionary/" + deviceType + "_rules.lvo"
|
|
ruleString = getRuleStringForVariable(deviceType, ruleFile, variableId)
|
|
retVal = validateValueAgainstRule(ruleString, variableValue)
|
|
return retVal
|
|
# EOM
|
|
|
|
|
|
def getRuleStringForVariable(deviceType, ruleFile, variableId):
|
|
retVal = ""
|
|
try:
|
|
# with open(ruleFile, 'r') as f:
|
|
f = open(ruleFile, 'r')
|
|
for line in f:
|
|
# debugOutput(line)
|
|
if(':' in line):
|
|
data = line.split(':')
|
|
# debugOutput(data[0])
|
|
if(data[0].strip() == variableId):
|
|
retVal = line
|
|
except Exception:
|
|
ruleString = cnos_devicerules.getRuleString(deviceType, variableId)
|
|
retVal = ruleString.strip()
|
|
return retVal
|
|
# EOM
|
|
|
|
|
|
def validateValueAgainstRule(ruleString, variableValue):
|
|
|
|
retVal = ""
|
|
if(ruleString == ""):
|
|
return 1
|
|
rules = ruleString.split(':')
|
|
variableType = rules[1].strip()
|
|
varRange = rules[2].strip()
|
|
if(variableType == "INTEGER"):
|
|
result = checkInteger(variableValue)
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-111"
|
|
elif(variableType == "FLOAT"):
|
|
result = checkFloat(variableValue)
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-112"
|
|
|
|
elif(variableType == "INTEGER_VALUE"):
|
|
int_range = varRange.split('-')
|
|
r = range(int(int_range[0].strip()), int(int_range[1].strip()))
|
|
if(checkInteger(variableValue) is not True):
|
|
return "Error-111"
|
|
result = int(variableValue) in r
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-113"
|
|
|
|
elif(variableType == "INTEGER_VALUE_RANGE"):
|
|
int_range = varRange.split('-')
|
|
varLower = int_range[0].strip()
|
|
varHigher = int_range[1].strip()
|
|
r = range(int(varLower), int(varHigher))
|
|
val_range = variableValue.split('-')
|
|
try:
|
|
valLower = val_range[0].strip()
|
|
valHigher = val_range[1].strip()
|
|
except Exception:
|
|
return "Error-113"
|
|
if((checkInteger(valLower) is not True) or
|
|
(checkInteger(valHigher) is not True)):
|
|
# debugOutput("Error-114")
|
|
return "Error-114"
|
|
result = (int(valLower) in r) and (int(valHigher)in r) \
|
|
and (int(valLower) < int(valHigher))
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
# debugOutput("Error-113")
|
|
return "Error-113"
|
|
|
|
elif(variableType == "INTEGER_OPTIONS"):
|
|
int_options = varRange.split(',')
|
|
if(checkInteger(variableValue) is not True):
|
|
return "Error-111"
|
|
for opt in int_options:
|
|
if(opt.strip() is variableValue):
|
|
result = True
|
|
break
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-115"
|
|
|
|
elif(variableType == "LONG"):
|
|
result = checkLong(variableValue)
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-116"
|
|
|
|
elif(variableType == "LONG_VALUE"):
|
|
long_range = varRange.split('-')
|
|
r = range(int(long_range[0].strip()), int(long_range[1].strip()))
|
|
if(checkLong(variableValue) is not True):
|
|
# debugOutput(variableValue)
|
|
return "Error-116"
|
|
result = int(variableValue) in r
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-113"
|
|
|
|
elif(variableType == "LONG_VALUE_RANGE"):
|
|
long_range = varRange.split('-')
|
|
r = range(int(long_range[0].strip()), int(long_range[1].strip()))
|
|
val_range = variableValue.split('-')
|
|
if((checkLong(val_range[0]) is not True) or
|
|
(checkLong(val_range[1]) is not True)):
|
|
return "Error-117"
|
|
result = (val_range[0] in r) and (
|
|
val_range[1] in r) and (val_range[0] < val_range[1])
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-113"
|
|
elif(variableType == "LONG_OPTIONS"):
|
|
long_options = varRange.split(',')
|
|
if(checkLong(variableValue) is not True):
|
|
return "Error-116"
|
|
for opt in long_options:
|
|
if(opt.strip() == variableValue):
|
|
result = True
|
|
break
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-115"
|
|
|
|
elif(variableType == "TEXT"):
|
|
if(variableValue == ""):
|
|
return "Error-118"
|
|
if(True is isinstance(variableValue, str)):
|
|
return "ok"
|
|
else:
|
|
return "Error-119"
|
|
|
|
elif(variableType == "NO_VALIDATION"):
|
|
if(variableValue == ""):
|
|
return "Error-118"
|
|
else:
|
|
return "ok"
|
|
|
|
elif(variableType == "TEXT_OR_EMPTY"):
|
|
if(variableValue is None or variableValue == ""):
|
|
return "ok"
|
|
if(result == isinstance(variableValue, str)):
|
|
return "ok"
|
|
else:
|
|
return "Error-119"
|
|
|
|
elif(variableType == "MATCH_TEXT"):
|
|
if(variableValue == ""):
|
|
return "Error-118"
|
|
if(isinstance(variableValue, str)):
|
|
if(varRange == variableValue):
|
|
return "ok"
|
|
else:
|
|
return "Error-120"
|
|
else:
|
|
return "Error-119"
|
|
|
|
elif(variableType == "MATCH_TEXT_OR_EMPTY"):
|
|
if(variableValue is None or variableValue == ""):
|
|
return "ok"
|
|
if(isinstance(variableValue, str)):
|
|
if(varRange == variableValue):
|
|
return "ok"
|
|
else:
|
|
return "Error-120"
|
|
else:
|
|
return "Error-119"
|
|
|
|
elif(variableType == "TEXT_OPTIONS"):
|
|
str_options = varRange.split(',')
|
|
if(isinstance(variableValue, str) is not True):
|
|
return "Error-119"
|
|
result = False
|
|
for opt in str_options:
|
|
if(opt.strip() == variableValue):
|
|
result = True
|
|
break
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-115"
|
|
|
|
elif(variableType == "TEXT_OPTIONS_OR_EMPTY"):
|
|
if(variableValue is None or variableValue == ""):
|
|
return "ok"
|
|
str_options = varRange.split(',')
|
|
if(isinstance(variableValue, str) is not True):
|
|
return "Error-119"
|
|
for opt in str_options:
|
|
if(opt.strip() == variableValue):
|
|
result = True
|
|
break
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-115"
|
|
|
|
elif(variableType == "IPV4Address"):
|
|
try:
|
|
socket.inet_pton(socket.AF_INET, variableValue)
|
|
result = True
|
|
except socket.error:
|
|
result = False
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-121"
|
|
elif(variableType == "IPV4AddressWithMask"):
|
|
if(variableValue is None or variableValue == ""):
|
|
return "Error-119"
|
|
str_options = variableValue.split('/')
|
|
ipaddr = str_options[0]
|
|
mask = str_options[1]
|
|
try:
|
|
socket.inet_pton(socket.AF_INET, ipaddr)
|
|
if(checkInteger(mask) is True):
|
|
result = True
|
|
else:
|
|
result = False
|
|
except socket.error:
|
|
result = False
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-121"
|
|
|
|
elif(variableType == "IPV6Address"):
|
|
try:
|
|
socket.inet_pton(socket.AF_INET6, variableValue)
|
|
result = True
|
|
except socket.error:
|
|
result = False
|
|
if(result is True):
|
|
return "ok"
|
|
else:
|
|
return "Error-122"
|
|
|
|
return retVal
|
|
# EOM
|
|
|
|
|
|
def disablePaging(remote_conn):
|
|
remote_conn.send("terminal length 0\n")
|
|
time.sleep(1)
|
|
# Clear the buffer on the screen
|
|
outputByte = remote_conn.recv(1000)
|
|
output = outputByte.decode()
|
|
return output
|
|
# EOM
|
|
|
|
|
|
def checkInteger(s):
|
|
try:
|
|
int(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
# EOM
|
|
|
|
|
|
def checkFloat(s):
|
|
try:
|
|
float(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
# EOM
|
|
|
|
|
|
def checkLong(s):
|
|
try:
|
|
int(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def debugOutput(command):
|
|
f = open('debugOutput.txt', 'a')
|
|
f.write(str(command)) # python will convert \n to os.linesep
|
|
f.close() # you can omit in most cases as the destructor will call it
|
|
# EOM
|