Lenovo cnos l3interface (#51322)

* Adding cnos_l3_interface module in alignment with others vendors.
This commit is contained in:
Anil Kumar Muraleedharan 2019-02-01 19:47:52 +05:30 committed by Nathaniel Case
parent f3907c977c
commit 004d8b03d4
12 changed files with 898 additions and 7 deletions

View file

@ -34,6 +34,7 @@
import time import time
import socket import socket
import re import re
import json
try: try:
from ansible.module_utils.network.cnos import cnos_errorcodes from ansible.module_utils.network.cnos import cnos_errorcodes
from ansible.module_utils.network.cnos import cnos_devicerules from ansible.module_utils.network.cnos import cnos_devicerules
@ -192,11 +193,23 @@ def run_cnos_commands(module, commands, check_rc=True):
return str(retVal) 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): def load_config(module, config):
try: try:
conn = get_connection(module) conn = get_connection(module)
conn.get('enable') conn.get('enable')
conn.edit_config(config) resp = conn.edit_config(config)
return resp.get('response')
except ConnectionError as exc: except ConnectionError as exc:
module.fail_json(msg=to_text(exc)) module.fail_json(msg=to_text(exc))

View file

@ -0,0 +1,424 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo, Inc.
# (c) 2019, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: cnos_l3_interface
version_added: "2.8"
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage Layer-3 interfaces on Lenovo CNOS network devices.
description:
- This module provides declarative management of Layer-3 interfaces
on CNOS network devices.
notes:
- Tested against CNOS 10.8.1
options:
name:
description:
- Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2
ipv4:
description:
- IPv4 address to be set for the Layer-3 interface mentioned in I(name)
option. The address format is <ipv4 address>/<mask>, the mask is number
in range 0-32 eg. 10.241.107.1/24
ipv6:
description:
- IPv6 address to be set for the Layer-3 interface mentioned in I(name)
option. The address format is <ipv6 address>/<mask>, the mask is number
in range 0-128 eg. fd5d:12c9:2201:1::1/64
aggregate:
description:
- List of Layer-3 interfaces definitions. Each of the entry in aggregate
list should define name of interface C(name) and a optional C(ipv4) or
C(ipv6) address.
state:
description:
- State of the Layer-3 interface configuration. It indicates if the
configuration should be present or absent on remote device.
default: present
choices: ['present', 'absent']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using
C(connection: network_cli)."
- For more information please see the
L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the
remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used
instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used
instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network
device for either connecting or sending commands. If the timeout
is exceeded before the operation is completed, the module will
error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not
specified in the task, the value of environment variable
C(ANSIBLE_NET_SSH_KEYFILE)will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the
value is not specified in the task, the value of environment
variable C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value
of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used
instead.
"""
EXAMPLES = """
- name: Remove Ethernet1/33 IPv4 and IPv6 address
cnos_l3_interface:
name: Ethernet1/33
state: absent
- name: Set Ethernet1/33 IPv4 address
cnos_l3_interface:
name: Ethernet1/33
ipv4: 10.241.107.1/24
- name: Set Ethernet1/33 IPv6 address
cnos_l3_interface:
name: Ethernet1/33
ipv6: "fd5d:12c9:2201:1::1/64"
- name: Set Ethernet1/33 in dhcp
cnos_l3_interface:
name: Ethernet1/33
ipv4: dhcp
ipv6: dhcp
- name: Set interface Vlan1 (SVI) IPv4 address
cnos_l3_interface:
name: Vlan1
ipv4: 192.168.0.5/24
- name: Set IP addresses on aggregate
cnos_l3_interface:
aggregate:
- { name: Ethernet1/33, ipv4: 10.241.107.1/24 }
- { name: GigabitEthernet1/33, ipv4: 10.241.107.1/24,
ipv6: "fd5d:12c9:2201:1::1/64" }
- name: Remove IP addresses on aggregate
cnos_l3_interface:
aggregate:
- { name: Ethernet1/33, ipv4: 10.241.107.1/24 }
- { name: Ethernet1/3``3, ipv4: 10.241.107.1/24,
ipv6: "fd5d:12c9:2201:1::1/64" }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to
manage the device.
type: list
sample:
- interface Ethernet1/33
- ip address 10.241.107.1 255.255.255.0
- ipv6 address fd5d:12c9:2201:1::1/64
"""
import re
from copy import deepcopy
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.cnos.cnos import get_config, load_config
from ansible.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible.module_utils.network.common.config import NetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.common.utils import is_netmask, is_masklen
from ansible.module_utils.network.common.utils import to_netmask, to_masklen
def validate_ipv4(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(
msg='address format is <ipv4 address>/<mask>,got invalid format %s' % value)
if not is_masklen(address[1]):
module.fail_json(
msg='invalid value for mask: %s, mask should be in range 0-32' % address[1])
def validate_ipv6(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(
msg='address format is <ipv6 address>/<mask>, got invalid format %s' % value)
else:
if not 0 <= int(address[1]) <= 128:
module.fail_json(
msg='invalid value for mask: %s, mask should be in range 0-128' % address[1])
def validate_param_values(module, obj, param=None):
if param is None:
param = module.params
for key in obj:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
values = []
matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M)
for match in matches:
match_str = match.group(1).strip()
if arg == 'ipv6 address':
values.append(match_str)
else:
values = match_str
break
return values or None
def search_obj_in_list(name, lst):
for o in lst:
if o['name'].lower() == name.lower():
return o
return None
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
for w in want:
name = w['name']
ipv4 = w['ipv4']
ipv6 = w['ipv6']
state = w['state']
interface = 'interface ' + name
commands.append(interface)
obj_in_have = search_obj_in_list(name, have)
if state == 'absent' and obj_in_have:
if obj_in_have['ipv4']:
if ipv4:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{0} {1}'.format(
address[0], to_netmask(address[1]))
commands.append('no ip address %s' % ipv4)
else:
commands.append('no ip address')
if obj_in_have['ipv6']:
if ipv6:
commands.append('no ipv6 address %s' % ipv6)
else:
commands.append('no ipv6 address')
if 'dhcp' in obj_in_have['ipv6']:
commands.append('no ipv6 address dhcp')
elif state == 'present':
if ipv4:
if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{0} {1}'.format(
address[0], to_netmask(address[1]))
commands.append('ip address %s' % ipv4)
if ipv6:
if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]:
commands.append('ipv6 address %s' % ipv6)
if commands[-1] == interface:
commands.pop(-1)
return commands
def map_config_to_obj(module):
config = get_config(module)
configobj = NetworkConfig(indent=1, contents=config)
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
ipv4 = parse_config_argument(configobj, item, 'ip address')
if ipv4:
# eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24
address = ipv4.strip().split(' ')
if len(address) == 2 and is_netmask(address[1]):
ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1])))
obj = {
'name': item,
'ipv4': ipv4,
'ipv6': parse_config_argument(configobj, item, 'ipv6 address'),
'state': 'present'
}
instances.append(obj)
return instances
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
validate_param_values(module, item, item)
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'],
'ipv4': module.params['ipv4'],
'ipv6': module.params['ipv6'],
'state': module.params['state']
})
validate_param_values(module, obj)
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
ipv4=dict(),
ipv6=dict(),
state=dict(default='present',
choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(cnos_argument_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
resp = load_config(module, commands)
if resp is not None:
warnings.extend((out for out in resp if out))
result['changed'] = True
if warnings:
result['warnings'] = warnings
if 'overlaps with address configured on' in warnings[0]:
result['failed'] = True
result['msg'] = warnings[0]
if 'Cannot set overlapping address' in warnings[0]:
result['failed'] = True
result['msg'] = warnings[0]
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -20,7 +20,7 @@ import re
import json import json
from itertools import chain from itertools import chain
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -76,12 +76,34 @@ class Cliconf(CliconfBase):
return self.send_command(cmd) return self.send_command(cmd)
@enable_mode @enable_mode
def edit_config(self, command): def edit_config(self, candidate=None, commit=True,
for cmd in chain(['configure terminal'], to_list(command), ['end']): replace=None, comment=None):
self.send_command(cmd) resp = {}
results = []
requests = []
if commit:
self.send_command('configure terminal')
for line in to_list(candidate):
if not isinstance(line, Mapping):
line = {'command': line}
def get(self, command, prompt=None, answer=None, sendonly=False, check_all=False): cmd = line['command']
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, check_all=check_all) if cmd != 'end' and cmd[0] != '!':
results.append(self.send_command(**line))
requests.append(cmd)
self.send_command('end')
else:
raise ValueError('check mode is not supported')
resp['request'] = requests
resp['response'] = results
return resp
def get(self, command, prompt=None, answer=None, sendonly=False,
check_all=False):
return self.send_command(command=command, prompt=prompt, answer=answer,
sendonly=sendonly, check_all=check_all)
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()

View file

@ -0,0 +1,2 @@
# No Lenovo Switch simulator yet, so not enabled
unsupported

View file

@ -0,0 +1,14 @@
# You have to paste this dummy information in /etc/ansible/hosts
# Notes:
# - Comments begin with the '#' character
# - Blank lines are ignored
# - Groups of hosts are delimited by [header] elements
# - You can enter hostnames or ip addresses
# - A hostname/ip can be a member of multiple groups
#
# In the /etc/ansible/hosts file u have to enter [cnos_l3_interface_sample] tag
# Following you should specify IP Adresses details
# Please change <username> and <password> with appropriate value for your switch.
[cnos_l3_interface_sample]
10.241.107.39 ansible_network_os=cnos ansible_ssh_user=admin ansible_ssh_pass=admin deviceType=g8272_cnos test_interface=ethernet1/33 test_interface2=ethernet1/44

View file

@ -0,0 +1,2 @@
---
testcase: "*"

View file

@ -0,0 +1,22 @@
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
register: test_cases
delegate_to: localhost
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include: "{{ test_case_to_run }}"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
- name: run test case (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
with_first_found: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

View file

@ -0,0 +1,277 @@
---
- debug: msg="START cnos_l3_interface cli/basic.yaml on connection={{ ansible_connection }}"
- name: Delete interface ipv4 and ipv6 address(setup)
cnos_l3_interface:
name: "{{ test_interface }}"
state: absent
provider: "{{ cli }}"
register: result
- name: Delete interface ipv4 and ipv6 address 2 (setup)
cnos_l3_interface:
name: "{{ test_interface2 }}"
state: absent
provider: "{{ cli }}"
register: result
- name: Setup - Ensure interfaces are switchport
cnos_config:
lines:
- no shutdown
parents:
- "interface {{ item }}"
provider: "{{ cli }}"
loop:
- "{{ test_interface }}"
- "{{ test_interface2 }}"
- name: Configure interface ipv4 address
cnos_l3_interface:
name: "{{ test_interface }}"
ipv4: 10.241.113.1/24
state: present
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ip address 10.241.113.1 255.255.255.0" in result.commands'
- name: Configure interface ipv4 address (idempotent)
cnos_l3_interface:
name: "{{ test_interface }}"
ipv4: 10.241.113.1/24
state: present
provider: "{{ cli }}"
register: result
- assert: &unchanged
that:
- 'result.changed == false'
- name: Assign same ipv4 address to other interface (fail)
cnos_l3_interface:
name: "{{ test_interface2 }}"
ipv4: 10.241.113.1/24
state: present
provider: "{{ cli }}"
ignore_errors: yes
register: result
- assert:
that:
- "result.failed == true"
- "result.msg is defined"
- name: Change interface ipv4 address
cnos_l3_interface:
name: "{{ test_interface }}"
ipv4: dhcp
state: present
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ip address dhcp" in result.commands'
- name: Configure interface ipv6 address
cnos_l3_interface: &ipv6-1
name: "{{ test_interface }}"
ipv6: fd5d:12c9:2201:1::1/64
state: present
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ipv6 address fd5d:12c9:2201:1::1/64" in result.commands'
- name: Configure interface ipv6 address (idempotent)
cnos_l3_interface: *ipv6-1
register: result
- assert:
that:
- 'result.changed == false'
- name: Configure second ipv6 address on interface
cnos_l3_interface: &ipv6-2
name: "{{ test_interface }}"
ipv6: fd5d:12c9:2291:1::1/64
state: present
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ipv6 address fd5d:12c9:2291:1::1/64" in result.commands'
- name: Ensure first ipv6 address still associated with interface
cnos_l3_interface: *ipv6-1
register: result
- assert:
that:
- 'result.changed == true'
- name: Ensure second ipv6 address still associated with interface
cnos_l3_interface: *ipv6-2
register: result
- assert:
that:
- 'result.changed == true'
- name: Assign same ipv6 address to other interface (fail)
cnos_l3_interface:
name: "{{ test_interface2 }}"
ipv6: fd5d:12c9:2201:1::1/64
state: present
provider: "{{ cli }}"
ignore_errors: yes
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface2 }}" in result.commands'
- '"ipv6 address fd5d:12c9:2201:1::1/64" in result.commands'
- name: Change interface ipv6 address
cnos_l3_interface:
name: "{{ test_interface }}"
ipv6: dhcp
state: present
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ipv6 address dhcp" in result.commands'
- name: Delete interface ipv4 and ipv6 address
cnos_l3_interface:
name: "{{ test_interface }}"
state: absent
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"no ip address" in result.commands'
- '"no ipv6 address" in result.commands'
- name: Delete interface ipv4 and ipv6 address (idempotent)
cnos_l3_interface:
name: "{{ test_interface }}"
state: absent
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == false'
- name: Delete second interface ipv4 and ipv6 address (setup)
cnos_l3_interface:
name: "{{ test_interface2 }}"
state: absent
provider: "{{ cli }}"
register: result
- name: Configure ipv4 and ipv6 address using aggregate
cnos_l3_interface:
aggregate:
- { name: "{{ test_interface }}", ipv4: 10.241.113.1/24, ipv6: "fd5d:12c9:2201:2::2/64" }
- { name: "{{ test_interface2 }}", ipv4: 10.141.233.2/16, ipv6: "fd5e:12c9:2201:3::3/32" }
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ip address 10.241.113.1 255.255.255.0" in result.commands'
- '"ipv6 address fd5d:12c9:2201:2::2/64" in result.commands'
- '"interface {{ test_interface2 }}" in result.commands'
- '"ip address 10.141.233.2 255.255.0.0" in result.commands'
- '"ipv6 address fd5e:12c9:2201:3::3/32" in result.commands'
- name: Configure ipv4 and ipv6 address using aggregate (idempotent)
cnos_l3_interface:
aggregate:
- { name: "{{ test_interface }}", ipv4: 10.241.113.1/24, ipv6: "fd5d:12c9:2201:2::2/64" }
- { name: "{{ test_interface2 }}", ipv4: 10.141.233.2/16, ipv6: "fd5e:12c9:2201:3::3/32" }
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == false'
- name: Change ipv4 and ipv6 address using aggregate
cnos_l3_interface:
aggregate:
- { name: "{{ test_interface }}", ipv4: 10.241.113.1/16, ipv6: "fd5a:12c9:2201:4::4/32" }
- { name: "{{ test_interface2 }}", ipv4: 10.141.233.2/24, ipv6: "fd5b:12c9:2201:5::5/90" }
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"ip address 10.241.113.1 255.255.0.0" in result.commands'
- '"ipv6 address fd5a:12c9:2201:4::4/32" in result.commands'
- '"interface {{ test_interface2 }}" in result.commands'
- '"ip address 10.141.233.2 255.255.255.0" in result.commands'
- '"ipv6 address fd5b:12c9:2201:5::5/90" in result.commands'
- name: Delete ipv4 and ipv6 address using aggregate
cnos_l3_interface:
aggregate:
- { name: "{{ test_interface }}" }
- { name: "{{ test_interface2 }}" }
state: absent
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == true'
- '"interface {{ test_interface }}" in result.commands'
- '"no ip address" in result.commands'
- '"no ipv6 address" in result.commands'
- '"interface {{ test_interface2 }}" in result.commands'
- '"no ip address" in result.commands'
- '"no ipv6 address" in result.commands'
- name: Delete ipv4 and ipv6 address using aggregate (idempotent)
cnos_l3_interface:
aggregate:
- { name: "{{ test_interface }}" }
- { name: "{{ test_interface2 }}" }
state: absent
provider: "{{ cli }}"
register: result
- assert:
that:
- 'result.changed == false'
- debug: msg="END cnos_l3_interface cli/basic.yaml on connection={{ ansible_connection }}"

View file

@ -0,0 +1,9 @@
---
cli:
host: "{{ inventory_hostname }}"
port: 22
username: admin
password: admin
timeout: 30
authorize: True
auth_pass:

View file

@ -0,0 +1,27 @@
!
version "10.8.0.42"
!
hostname ip10-241-107-39
!
vlan 13
name dave
!
interface Ethernet1/9
ip address 10.201.107.1 255.255.255.0
ipv6 address dead::beaf/64
description Bleh
!
interface Ethernet1/33
description Hentammoo
load-interval counter 2 33
switchport access vlan 33
storm-control broadcast level 12.50
mtu 66
microburst-detection enable threshold 25
lldp tlv-select max-frame-size
lacp port-priority 33
spanning-tree mst 33-35 cost 33
spanning-tree bpduguard enable
!
!
end

View file

@ -0,0 +1,77 @@
#
# (c) 2018 Lenovo.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
import json
from units.compat.mock import patch
from ansible.modules.network.cnos import cnos_l3_interface
from units.modules.utils import set_module_args
from .cnos_module import TestCnosModule, load_fixture
class TestCnosL3InterfaceModule(TestCnosModule):
module = cnos_l3_interface
def setUp(self):
super(TestCnosL3InterfaceModule, self).setUp()
self._patch_get_config = patch(
'ansible.modules.network.cnos.cnos_l3_interface.get_config'
)
self._patch_load_config = patch(
'ansible.modules.network.cnos.cnos_l3_interface.load_config'
)
self._get_config = self._patch_get_config.start()
self._load_config = self._patch_load_config.start()
def tearDown(self):
super(TestCnosL3InterfaceModule, self).tearDown()
self._patch_get_config.stop()
self._patch_load_config.stop()
def load_fixtures(self, commands=None):
config_file = 'l3_interface_config.cfg'
self._get_config.return_value = load_fixture(config_file)
self._load_config.return_value = None
def test_cnos_l3_interface_ipv4_address(self, *args, **kwargs):
set_module_args(dict(
name='Ethernet 1/35',
ipv4='192.168.4.1/24'
))
commands = [
'interface Ethernet 1/35',
'ip address 192.168.4.1 255.255.255.0'
]
result = self.execute_module(changed=True, commands=commands)
def test_cnos_l3_interface_absent(self, *args, **kwargs):
set_module_args(dict(
name='Ethernet1/9',
state='absent'
))
commands = [
'interface Ethernet1/9',
'no ip address',
'no ipv6 address'
]
result = self.execute_module(changed=True, commands=commands)