mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 22:02:50 +00:00
Mellanox OS name change: MLNXOS changed to ONYX (#34753)
* Mellanox OS name change: MLNXOS changed to ONYX Signed-off-by: Samer Deeb <samerd@mellanox.com> * Fix alphabetical order of modules metadata Signed-off-by: Samer Deeb <samerd@mellanox.com>
This commit is contained in:
committed by
John R Barker
parent
57ff84251e
commit
f8884f12bc
0
lib/ansible/modules/network/onyx/__init__.py
Normal file
0
lib/ansible/modules/network/onyx/__init__.py
Normal file
256
lib/ansible/modules/network/onyx/onyx_bgp.py
Normal file
256
lib/ansible/modules/network/onyx/onyx_bgp.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_bgp
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Configures BGP on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of BGP router and neighbors
|
||||
on Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
as_number:
|
||||
description:
|
||||
- Local AS number.
|
||||
required: true
|
||||
router_id:
|
||||
description:
|
||||
- Router IP address. Required if I(state=present).
|
||||
neighbors:
|
||||
description:
|
||||
- List of neighbors. Required if I(state=present).
|
||||
suboptions:
|
||||
remote_as:
|
||||
description:
|
||||
- Remote AS number.
|
||||
required: true
|
||||
neighbor:
|
||||
description:
|
||||
- Neighbor IP address.
|
||||
required: true
|
||||
networks:
|
||||
description:
|
||||
- List of advertised networks.
|
||||
state:
|
||||
description:
|
||||
- BGP state.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure bgp
|
||||
onyx_bgp:
|
||||
as_number: 320
|
||||
router_id: 10.3.3.3
|
||||
neighbors:
|
||||
- remote_as: 321
|
||||
neighbor: 10.3.3.4
|
||||
state: present
|
||||
networks:
|
||||
- 172.16.1.0/24
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- router bgp 172
|
||||
- exit
|
||||
- router bgp 172 router-id 2.3.4.5 force
|
||||
- router bgp 172 neighbor 2.3.4.6 remote-as 173
|
||||
- router bgp 172 network 172.16.1.0 /24
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import get_bgp_summary
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
|
||||
|
||||
class OnyxBgpModule(BaseOnyxModule):
|
||||
LOCAL_AS_REGEX = re.compile(r'^\s+router bgp\s+(\d+).*')
|
||||
ROUTER_ID_REGEX = re.compile(
|
||||
r'^\s+router bgp\s+(\d+).*router-id\s+(\S+)\s+.*')
|
||||
NEIGHBOR_REGEX = re.compile(
|
||||
r'^\s+router bgp\s+(\d+).*neighbor\s+(\S+)\s+remote\-as\s+(\S+).*')
|
||||
NETWORK_REGEX = re.compile(
|
||||
r'^\s+router bgp\s+(\d+).*network\s+(\S+)\s+(\S+).*')
|
||||
|
||||
def init_module(self):
|
||||
""" initialize module
|
||||
"""
|
||||
neighbor_spec = dict(
|
||||
remote_as=dict(type='int', required=True),
|
||||
neighbor=dict(required=True),
|
||||
)
|
||||
element_spec = dict(
|
||||
as_number=dict(type='int', required=True),
|
||||
router_id=dict(),
|
||||
neighbors=dict(type='list', elements='dict',
|
||||
options=neighbor_spec),
|
||||
networks=dict(type='list', elements='str'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
argument_spec = dict()
|
||||
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
module_params = self._module.params
|
||||
req_neighbors = list()
|
||||
self._required_config = dict(
|
||||
as_number=module_params['as_number'],
|
||||
router_id=module_params['router_id'],
|
||||
state=module_params['state'],
|
||||
neighbors=req_neighbors,
|
||||
networks=module_params['networks'])
|
||||
neighbors = module_params['neighbors'] or list()
|
||||
for neighbor_data in neighbors:
|
||||
req_neighbors.append(
|
||||
(neighbor_data['neighbor'], neighbor_data['remote_as']))
|
||||
self.validate_param_values(self._required_config)
|
||||
|
||||
def _set_bgp_config(self, bgp_config):
|
||||
lines = bgp_config.split('\n')
|
||||
self._current_config['router_id'] = None
|
||||
self._current_config['as_number'] = None
|
||||
neighbors = self._current_config['neighbors'] = []
|
||||
networks = self._current_config['networks'] = []
|
||||
for line in lines:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
if not self._current_config['as_number']:
|
||||
match = self.LOCAL_AS_REGEX.match(line)
|
||||
if match:
|
||||
self._current_config['as_number'] = int(match.group(1))
|
||||
continue
|
||||
if not self._current_config['router_id']:
|
||||
match = self.ROUTER_ID_REGEX.match(line)
|
||||
if match:
|
||||
self._current_config['router_id'] = match.group(2)
|
||||
continue
|
||||
match = self.NEIGHBOR_REGEX.match(line)
|
||||
if match:
|
||||
neighbors.append((match.group(2), int(match.group(3))))
|
||||
continue
|
||||
match = self.NETWORK_REGEX.match(line)
|
||||
if match:
|
||||
network = match.group(2) + match.group(3)
|
||||
networks.append(network)
|
||||
continue
|
||||
|
||||
def _get_bgp_summary(self):
|
||||
return get_bgp_summary(self._module)
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
bgp_config = self._get_bgp_summary()
|
||||
if bgp_config:
|
||||
self._set_bgp_config(bgp_config)
|
||||
|
||||
def generate_commands(self):
|
||||
state = self._required_config['state']
|
||||
if state == 'present':
|
||||
self._generate_bgp_cmds()
|
||||
else:
|
||||
self._generate_no_bgp_cmds()
|
||||
|
||||
def _generate_bgp_cmds(self):
|
||||
as_number = self._required_config['as_number']
|
||||
curr_as_num = self._current_config.get('as_number')
|
||||
bgp_removed = False
|
||||
if curr_as_num != as_number:
|
||||
if curr_as_num:
|
||||
self._commands.append('no router bgp %d' % curr_as_num)
|
||||
bgp_removed = True
|
||||
self._commands.append('router bgp %d' % as_number)
|
||||
self._commands.append('exit')
|
||||
curr_route_id = self._current_config.get('router_id')
|
||||
req_router_id = self._required_config['router_id']
|
||||
if req_router_id and req_router_id != curr_route_id or bgp_removed:
|
||||
self._commands.append('router bgp %d router-id %s force' %
|
||||
(as_number, req_router_id))
|
||||
self._generate_neighbors_cmds(as_number, bgp_removed)
|
||||
self._generate_networks_cmds(as_number, bgp_removed)
|
||||
|
||||
def _generate_neighbors_cmds(self, as_number, bgp_removed):
|
||||
req_neighbors = self._required_config['neighbors']
|
||||
curr_neighbors = self._current_config.get('neighbors', [])
|
||||
if not bgp_removed:
|
||||
for neighbor_data in curr_neighbors:
|
||||
if neighbor_data not in req_neighbors:
|
||||
(neighbor, remote_as) = neighbor_data
|
||||
self._commands.append(
|
||||
'router bgp %s no neighbor %s remote-as %s' %
|
||||
(as_number, neighbor, remote_as))
|
||||
|
||||
for neighbor_data in req_neighbors:
|
||||
if bgp_removed or neighbor_data not in curr_neighbors:
|
||||
(neighbor, remote_as) = neighbor_data
|
||||
self._commands.append(
|
||||
'router bgp %s neighbor %s remote-as %s' %
|
||||
(as_number, neighbor, remote_as))
|
||||
|
||||
def _generate_networks_cmds(self, as_number, bgp_removed):
|
||||
req_networks = self._required_config['networks'] or []
|
||||
curr_networks = self._current_config.get('networks', [])
|
||||
if not bgp_removed:
|
||||
for network in curr_networks:
|
||||
if network not in req_networks:
|
||||
net_attrs = network.split('/')
|
||||
if len(net_attrs) != 2:
|
||||
self._module.fail_json(
|
||||
msg='Invalid network %s' % network)
|
||||
|
||||
net_address, netmask = net_attrs
|
||||
cmd = 'router bgp %s no network %s /%s' % (
|
||||
as_number, net_address, netmask)
|
||||
self._commands.append(cmd)
|
||||
|
||||
for network in req_networks:
|
||||
if bgp_removed or network not in curr_networks:
|
||||
net_attrs = network.split('/')
|
||||
if len(net_attrs) != 2:
|
||||
self._module.fail_json(
|
||||
msg='Invalid network %s' % network)
|
||||
net_address, netmask = net_attrs
|
||||
cmd = 'router bgp %s network %s /%s' % (
|
||||
as_number, net_address, netmask)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def _generate_no_bgp_cmds(self):
|
||||
as_number = self._required_config['as_number']
|
||||
curr_as_num = self._current_config.get('as_number')
|
||||
if curr_as_num and curr_as_num == as_number:
|
||||
self._commands.append('no router bgp %d' % as_number)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxBgpModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
215
lib/ansible/modules/network/onyx/onyx_command.py
Normal file
215
lib/ansible/modules/network/onyx/onyx_command.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_command
|
||||
extends_documentation_fragment: onyx
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Run commands on remote devices running Mellanox ONYX
|
||||
description:
|
||||
- Sends arbitrary commands to an Mellanox ONYX network device and returns
|
||||
the results read from the device. This module includes an
|
||||
argument that will cause the module to wait for a specific condition
|
||||
before returning or timing out if the condition is not met.
|
||||
- This module does not support running commands in configuration mode.
|
||||
Please use M(onyx_config) to configure Mellanox ONYX devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
commands:
|
||||
description:
|
||||
- List of commands to send to the remote Mellanox ONYX network device.
|
||||
The resulting output from the command
|
||||
is returned. If the I(wait_for) argument is provided, the
|
||||
module is not returned until the condition is satisfied or
|
||||
the number of retries has expired.
|
||||
required: true
|
||||
wait_for:
|
||||
description:
|
||||
- List of conditions to evaluate against the output of the
|
||||
command. The task will wait for each condition to be true
|
||||
before moving forward. If the conditional is not true
|
||||
within the configured number of retries, the task fails.
|
||||
See examples.
|
||||
match:
|
||||
description:
|
||||
- The I(match) argument is used in conjunction with the
|
||||
I(wait_for) argument to specify the match policy. Valid
|
||||
values are C(all) or C(any). If the value is set to C(all)
|
||||
then all conditionals in the wait_for must be satisfied. If
|
||||
the value is set to C(any) then only one of the values must be
|
||||
satisfied.
|
||||
default: all
|
||||
choices: ['any', 'all']
|
||||
retries:
|
||||
description:
|
||||
- Specifies the number of retries a command should by tried
|
||||
before it is considered failed. The command is run on the
|
||||
target device every retry and evaluated against the
|
||||
I(wait_for) conditions.
|
||||
default: 10
|
||||
interval:
|
||||
description:
|
||||
- Configures the interval in seconds to wait between retries
|
||||
of the command. If the command does not pass the specified
|
||||
conditions, the interval indicates how long to wait before
|
||||
trying the command again.
|
||||
default: 1
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
tasks:
|
||||
- name: run show version on remote devices
|
||||
onyx_command:
|
||||
commands: show version
|
||||
|
||||
- name: run show version and check to see if output contains MLNXOS
|
||||
onyx_command:
|
||||
commands: show version
|
||||
wait_for: result[0] contains MLNXOS
|
||||
|
||||
- name: run multiple commands on remote nodes
|
||||
onyx_command:
|
||||
commands:
|
||||
- show version
|
||||
- show interfaces
|
||||
|
||||
- name: run multiple commands and evaluate the output
|
||||
onyx_command:
|
||||
commands:
|
||||
- show version
|
||||
- show interfaces
|
||||
wait_for:
|
||||
- result[0] contains MLNXOS
|
||||
- result[1] contains mgmt1
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
stdout:
|
||||
description: The set of responses from the commands
|
||||
returned: always apart from low level errors (such as action plugin)
|
||||
type: list
|
||||
sample: ['...', '...']
|
||||
stdout_lines:
|
||||
description: The value of stdout split into a list
|
||||
returned: always apart from low level errors (such as action plugin)
|
||||
type: list
|
||||
sample: [['...', '...'], ['...'], ['...']]
|
||||
failed_conditions:
|
||||
description: The list of conditionals that have failed
|
||||
returned: failed
|
||||
type: list
|
||||
sample: ['...', '...']
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.parsing import Conditional
|
||||
from ansible.module_utils.network.common.utils import ComplexList
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import run_commands
|
||||
|
||||
|
||||
def to_lines(stdout):
|
||||
for item in stdout:
|
||||
if isinstance(item, string_types):
|
||||
item = str(item).split('\n')
|
||||
yield item
|
||||
|
||||
|
||||
def parse_commands(module, warnings):
|
||||
command = ComplexList(dict(
|
||||
command=dict(key=True),
|
||||
prompt=dict(),
|
||||
answer=dict()
|
||||
), module)
|
||||
commands = command(module.params['commands'])
|
||||
for item in list(commands):
|
||||
if module.check_mode and not item['command'].startswith('show'):
|
||||
warnings.append(
|
||||
'only show commands are supported when using check mode, not '
|
||||
'executing `%s`' % item['command']
|
||||
)
|
||||
commands.remove(item)
|
||||
elif item['command'].startswith('conf'):
|
||||
module.fail_json(
|
||||
msg='onyx_command does not support running config mode '
|
||||
'commands. Please use onyx_config instead'
|
||||
)
|
||||
return commands
|
||||
|
||||
|
||||
def main():
|
||||
"""main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
commands=dict(type='list', required=True),
|
||||
|
||||
wait_for=dict(type='list'),
|
||||
match=dict(default='all', choices=['all', 'any']),
|
||||
|
||||
retries=dict(default=10, type='int'),
|
||||
interval=dict(default=1, type='int')
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
warnings = list()
|
||||
commands = parse_commands(module, warnings)
|
||||
result['warnings'] = warnings
|
||||
|
||||
wait_for = module.params['wait_for'] or list()
|
||||
conditionals = [Conditional(c) for c in wait_for]
|
||||
|
||||
retries = module.params['retries']
|
||||
interval = module.params['interval']
|
||||
match = module.params['match']
|
||||
|
||||
while retries > 0:
|
||||
responses = run_commands(module, commands)
|
||||
|
||||
for item in list(conditionals):
|
||||
if item(responses):
|
||||
if match == 'any':
|
||||
conditionals = list()
|
||||
break
|
||||
conditionals.remove(item)
|
||||
|
||||
if not conditionals:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
retries -= 1
|
||||
|
||||
if conditionals:
|
||||
failed_conditions = [item.raw for item in conditionals]
|
||||
msg = 'One or more conditional statements have not be satisfied'
|
||||
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||
|
||||
result.update({
|
||||
'changed': False,
|
||||
'stdout': responses,
|
||||
'stdout_lines': list(to_lines(responses))
|
||||
})
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
229
lib/ansible/modules/network/onyx/onyx_config.py
Normal file
229
lib/ansible/modules/network/onyx/onyx_config.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_config
|
||||
extends_documentation_fragment: onyx
|
||||
version_added: "2.5"
|
||||
author: "Alex Tabachnik (@atabachnik), Samer Deeb (@samerd)"
|
||||
short_description: Manage Mellanox ONYX configuration sections
|
||||
description:
|
||||
- Mellanox ONYX configurations uses a simple block indent file syntax
|
||||
for segmenting configuration into sections. This module provides
|
||||
an implementation for working with ONYX configuration sections in
|
||||
a deterministic way.
|
||||
options:
|
||||
lines:
|
||||
description:
|
||||
- The ordered set of commands that should be configured in the
|
||||
section. The commands must be the exact same commands as found
|
||||
in the device running-config. Be sure to note the configuration
|
||||
command syntax as some commands are automatically modified by the
|
||||
device config parser.
|
||||
aliases: ['commands']
|
||||
parents:
|
||||
description:
|
||||
- The ordered set of parents that uniquely identify the section
|
||||
the commands should be checked against. If the parents argument
|
||||
is omitted, the commands are checked against the set of top
|
||||
level or global commands.
|
||||
src:
|
||||
description:
|
||||
- Specifies the source path to the file that contains the configuration
|
||||
or configuration template to load. The path to the source file can
|
||||
either be the full path on the Ansible control host or a relative
|
||||
path from the playbook or role root directory. This argument is mutually
|
||||
exclusive with I(lines), I(parents).
|
||||
before:
|
||||
description:
|
||||
- The ordered set of commands to push on to the command stack if
|
||||
a change needs to be made. This allows the playbook designer
|
||||
the opportunity to perform configuration commands prior to pushing
|
||||
any changes without affecting how the set of commands are matched
|
||||
against the system.
|
||||
after:
|
||||
description:
|
||||
- The ordered set of commands to append to the end of the command
|
||||
stack if a change needs to be made. Just like with I(before) this
|
||||
allows the playbook designer to append a set of commands to be
|
||||
executed after the command set.
|
||||
match:
|
||||
description:
|
||||
- Instructs the module on the way to perform the matching of
|
||||
the set of commands against the current device config. If
|
||||
match is set to I(line), commands are matched line by line. If
|
||||
match is set to I(strict), command lines are matched with respect
|
||||
to position. If match is set to I(exact), command lines
|
||||
must be an equal match. Finally, if match is set to I(none), the
|
||||
module will not attempt to compare the source configuration with
|
||||
the running configuration on the remote device.
|
||||
default: line
|
||||
choices: ['line', 'strict', 'exact', 'none']
|
||||
replace:
|
||||
description:
|
||||
- Instructs the module on the way to perform the configuration
|
||||
on the device. If the replace argument is set to I(line) then
|
||||
the modified lines are pushed to the device in configuration
|
||||
mode. If the replace argument is set to I(block) then the entire
|
||||
command block is pushed to the device in configuration mode if any
|
||||
line is not correct
|
||||
default: line
|
||||
choices: ['line', 'block']
|
||||
backup:
|
||||
description:
|
||||
- This argument will cause the module to create a full backup of
|
||||
the current C(running-config) from the remote device before any
|
||||
changes are made. The backup file is written to the C(backup)
|
||||
folder in the playbook root directory. If the directory does not
|
||||
exist, it is created.
|
||||
default: no
|
||||
choices: ['yes', 'no']
|
||||
config:
|
||||
description:
|
||||
- The C(config) argument allows the playbook designer to supply
|
||||
the base configuration to be used to validate configuration
|
||||
changes necessary. If this argument is provided, the module
|
||||
will not download the running-config from the remote node.
|
||||
save:
|
||||
description:
|
||||
- The C(save) argument instructs the module to save the running-
|
||||
config to the startup-config at the conclusion of the module
|
||||
running. If check mode is specified, this argument is ignored.
|
||||
default: no
|
||||
choices: ['yes', 'no']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- onyx_config:
|
||||
lines:
|
||||
- snmp-server community
|
||||
- snmp-server host 10.2.2.2 traps version 2c
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
updates:
|
||||
description: The set of commands that will be pushed to the remote device
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['...', '...']
|
||||
backup_path:
|
||||
description: The full path to the backup file
|
||||
returned: when backup is yes
|
||||
type: string
|
||||
sample: /playbooks/ansible/backup/onyx_config.2016-07-16@22:28:34
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import get_config
|
||||
from ansible.module_utils.network.onyx.onyx import load_config
|
||||
from ansible.module_utils.network.onyx.onyx import run_commands
|
||||
|
||||
|
||||
def get_candidate(module):
|
||||
candidate = NetworkConfig(indent=1)
|
||||
if module.params['src']:
|
||||
candidate.load(module.params['src'])
|
||||
elif module.params['lines']:
|
||||
parents = module.params['parents'] or list()
|
||||
candidate.add(module.params['lines'], parents=parents)
|
||||
return candidate
|
||||
|
||||
|
||||
def run(module, result):
|
||||
match = module.params['match']
|
||||
replace = module.params['replace']
|
||||
path = module.params['parents']
|
||||
|
||||
candidate = get_candidate(module)
|
||||
if match != 'none':
|
||||
contents = module.params['config']
|
||||
if not contents:
|
||||
contents = get_config(module)
|
||||
config = NetworkConfig(indent=1, contents=contents)
|
||||
configobjs = candidate.difference(config, path=path, match=match,
|
||||
replace=replace)
|
||||
|
||||
else:
|
||||
configobjs = candidate.items
|
||||
|
||||
if configobjs:
|
||||
commands = dumps(configobjs, 'commands').split('\n')
|
||||
|
||||
if module.params['lines']:
|
||||
if module.params['before']:
|
||||
commands[:0] = module.params['before']
|
||||
|
||||
if module.params['after']:
|
||||
commands.extend(module.params['after'])
|
||||
|
||||
result['updates'] = commands
|
||||
|
||||
# send the configuration commands to the device and merge
|
||||
# them with the current running config
|
||||
if not module.check_mode:
|
||||
load_config(module, commands)
|
||||
result['changed'] = True
|
||||
|
||||
if module.params['save']:
|
||||
if not module.check_mode:
|
||||
run_commands(module, 'configuration write')
|
||||
result['changed'] = True
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
src=dict(type='path'),
|
||||
|
||||
lines=dict(aliases=['commands'], type='list'),
|
||||
parents=dict(type='list'),
|
||||
|
||||
before=dict(type='list'),
|
||||
after=dict(type='list'),
|
||||
|
||||
match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
|
||||
replace=dict(default='line', choices=['line', 'block']),
|
||||
|
||||
config=dict(),
|
||||
|
||||
backup=dict(type='bool', default=False),
|
||||
save=dict(type='bool', default=False),
|
||||
)
|
||||
|
||||
mutually_exclusive = [('lines', 'src'),
|
||||
('parents', 'src')]
|
||||
|
||||
required_if = [('match', 'strict', ['lines']),
|
||||
('match', 'exact', ['lines']),
|
||||
('replace', 'block', ['lines'])]
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
if module.params['backup']:
|
||||
result['__backup__'] = get_config(module)
|
||||
|
||||
run(module, result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
238
lib/ansible/modules/network/onyx/onyx_facts.py
Normal file
238
lib/ansible/modules/network/onyx/onyx_facts.py
Normal file
@@ -0,0 +1,238 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_facts
|
||||
version_added: "2.5"
|
||||
author: "Waleed Mousa (@waleedym), Samer Deeb (@samerd)"
|
||||
short_description: Collect facts from Mellanox ONYX network devices
|
||||
description:
|
||||
- Collects a base set of device facts from a ONYX Mellanox network devices
|
||||
This module prepends all of the base network fact keys with
|
||||
C(ansible_net_<fact>). The facts module will always collect a base set of
|
||||
facts from the device and can enable or disable collection of additional
|
||||
facts.
|
||||
notes:
|
||||
- Tested against ONYX 3.6
|
||||
options:
|
||||
gather_subset:
|
||||
description:
|
||||
- When supplied, this argument will restrict the facts collected
|
||||
to a given subset. Possible values for this argument include
|
||||
all, version, module, and interfaces. Can specify a list of
|
||||
values to include a larger subset. Values can also be used
|
||||
with an initial C(M(!)) to specify that a specific subset should
|
||||
not be collected.
|
||||
required: false
|
||||
default: version
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Collect all facts from the device
|
||||
onyx_facts:
|
||||
gather_subset: all
|
||||
|
||||
- name: Collect only the interfaces facts
|
||||
onyx_facts:
|
||||
gather_subset:
|
||||
- interfaces
|
||||
|
||||
- name: Do not collect version facts
|
||||
onyx_facts:
|
||||
gather_subset:
|
||||
- "!version"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
ansible_net_gather_subset:
|
||||
description: The list of fact subsets collected from the device
|
||||
returned: always
|
||||
type: list
|
||||
|
||||
# version
|
||||
ansible_net_version:
|
||||
description: A hash of all curently running system image information
|
||||
returned: when version is configured or when no gather_subset is provided
|
||||
type: dict
|
||||
|
||||
# modules
|
||||
ansible_net_modules:
|
||||
description: A hash of all modules on the systeme with status
|
||||
returned: when modules is configured
|
||||
type: dict
|
||||
|
||||
# interfaces
|
||||
ansible_net_interfaces:
|
||||
description: A hash of all interfaces running on the system
|
||||
returned: when interfaces is configured
|
||||
type: dict
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxFactsModule(BaseOnyxModule):
|
||||
|
||||
def get_runable_subset(self, gather_subset):
|
||||
runable_subsets = set()
|
||||
exclude_subsets = set()
|
||||
for subset in gather_subset:
|
||||
if subset == 'all':
|
||||
runable_subsets.update(VALID_SUBSETS)
|
||||
continue
|
||||
|
||||
if subset.startswith('!'):
|
||||
subset = subset[1:]
|
||||
if subset == 'all':
|
||||
exclude_subsets.update(VALID_SUBSETS)
|
||||
continue
|
||||
exclude = True
|
||||
else:
|
||||
exclude = False
|
||||
|
||||
if subset not in VALID_SUBSETS:
|
||||
self._module.fail_json(msg='Bad subset')
|
||||
|
||||
if exclude:
|
||||
exclude_subsets.add(subset)
|
||||
else:
|
||||
runable_subsets.add(subset)
|
||||
|
||||
if not runable_subsets:
|
||||
runable_subsets.update(VALID_SUBSETS)
|
||||
|
||||
runable_subsets.difference_update(exclude_subsets)
|
||||
if not runable_subsets:
|
||||
runable_subsets.add('version')
|
||||
return runable_subsets
|
||||
|
||||
def init_module(self):
|
||||
""" module intialization
|
||||
"""
|
||||
argument_spec = dict(
|
||||
gather_subset=dict(default=['version'], type='list')
|
||||
)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def run(self):
|
||||
self.init_module()
|
||||
gather_subset = self._module.params['gather_subset']
|
||||
runable_subsets = self.get_runable_subset(gather_subset)
|
||||
facts = dict()
|
||||
facts['gather_subset'] = list(runable_subsets)
|
||||
|
||||
instances = list()
|
||||
for key in runable_subsets:
|
||||
facter_cls = FACT_SUBSETS[key]
|
||||
instances.append(facter_cls(self._module))
|
||||
|
||||
for inst in instances:
|
||||
inst.populate()
|
||||
facts.update(inst.facts)
|
||||
|
||||
ansible_facts = dict()
|
||||
for key, value in iteritems(facts):
|
||||
key = 'ansible_net_%s' % key
|
||||
ansible_facts[key] = value
|
||||
self._module.exit_json(ansible_facts=ansible_facts)
|
||||
|
||||
|
||||
class FactsBase(object):
|
||||
|
||||
COMMANDS = ['']
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.facts = dict()
|
||||
self.responses = None
|
||||
|
||||
def _show_cmd(self, cmd):
|
||||
return show_cmd(self.module, cmd, json_fmt=True)
|
||||
|
||||
def populate(self):
|
||||
self.responses = []
|
||||
for cmd in self.COMMANDS:
|
||||
self.responses.append(self._show_cmd(cmd))
|
||||
|
||||
|
||||
class Version(FactsBase):
|
||||
|
||||
COMMANDS = ['show version']
|
||||
|
||||
def populate(self):
|
||||
super(Version, self).populate()
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
self.facts['version'] = data
|
||||
|
||||
|
||||
class Module(FactsBase):
|
||||
|
||||
COMMANDS = ['show module']
|
||||
|
||||
def populate(self):
|
||||
super(Module, self).populate()
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
self.facts['modules'] = data
|
||||
|
||||
|
||||
class Interfaces(FactsBase):
|
||||
|
||||
COMMANDS = ['show interfaces ethernet']
|
||||
|
||||
def populate(self):
|
||||
super(Interfaces, self).populate()
|
||||
|
||||
data = self.responses[0]
|
||||
if data:
|
||||
self.facts['interfaces'] = self.populate_interfaces(data)
|
||||
|
||||
def populate_interfaces(self, interfaces):
|
||||
interfaces_dict = dict()
|
||||
for if_data in interfaces:
|
||||
if_dict = dict()
|
||||
if_dict["MAC Address"] = if_data["Mac address"]
|
||||
if_dict["Actual Speed"] = if_data["Actual speed"]
|
||||
if_dict["MTU"] = if_data["MTU"]
|
||||
if_dict["Admin State"] = if_data["Admin state"]
|
||||
if_dict["Operational State"] = if_data["Operational state"]
|
||||
if_name = if_dict["Interface Name"] = if_data["header"]
|
||||
interfaces_dict[if_name] = if_dict
|
||||
return interfaces_dict
|
||||
|
||||
|
||||
FACT_SUBSETS = dict(
|
||||
version=Version,
|
||||
modules=Module,
|
||||
interfaces=Interfaces
|
||||
)
|
||||
|
||||
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxFactsModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
482
lib/ansible/modules/network/onyx/onyx_interface.py
Normal file
482
lib/ansible/modules/network/onyx/onyx_interface.py
Normal file
@@ -0,0 +1,482 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_interface
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage Interfaces on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of Interfaces
|
||||
on Mellanox ONYX network devices.
|
||||
notes:
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the Interface.
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- Description of Interface.
|
||||
enabled:
|
||||
description:
|
||||
- Interface link status.
|
||||
type: bool
|
||||
speed:
|
||||
description:
|
||||
- Interface link speed.
|
||||
choices: ['1G', '10G', '25G', '40G', '50G', '56G', '100G']
|
||||
mtu:
|
||||
description:
|
||||
- Maximum size of transmit packet.
|
||||
aggregate:
|
||||
description: List of Interfaces definitions.
|
||||
duplex:
|
||||
description:
|
||||
- Interface link status
|
||||
default: auto
|
||||
choices: ['full', 'half', 'auto']
|
||||
tx_rate:
|
||||
description:
|
||||
- Transmit rate in bits per second (bps).
|
||||
rx_rate:
|
||||
description:
|
||||
- Receiver rate in bits per second (bps).
|
||||
delay:
|
||||
description:
|
||||
- Time in seconds to wait before checking for the operational state on
|
||||
remote device. This wait is applicable for operational state argument
|
||||
which are I(state) with values C(up)/C(down).
|
||||
default: 10
|
||||
purge:
|
||||
description:
|
||||
- Purge Interfaces not defined in the aggregate parameter.
|
||||
This applies only for logical interface.
|
||||
default: false
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- State of the Interface configuration, C(up) means present and
|
||||
operationally up and C(down) means present and operationally C(down)
|
||||
default: present
|
||||
choices: ['present', 'absent', 'up', 'down']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure interface
|
||||
onyx_interface:
|
||||
name: Eth1/2
|
||||
description: test-interface
|
||||
speed: 100 GB
|
||||
mtu: 512
|
||||
|
||||
- name: make interface up
|
||||
onyx_interface:
|
||||
name: Eth1/2
|
||||
enabled: True
|
||||
|
||||
- name: make interface down
|
||||
onyx_interface:
|
||||
name: Eth1/2
|
||||
enabled: False
|
||||
|
||||
- name: Check intent arguments
|
||||
onyx_interface:
|
||||
name: Eth1/2
|
||||
state: up
|
||||
|
||||
- name: Config + intent
|
||||
onyx_interface:
|
||||
name: Eth1/2
|
||||
enabled: False
|
||||
state: down
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- interface ethernet 1/2
|
||||
- description test-interface
|
||||
- mtu 512
|
||||
- exit
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
import re
|
||||
from time import sleep
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import conditional
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
|
||||
|
||||
|
||||
class OnyxInterfaceModule(BaseOnyxModule):
|
||||
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
|
||||
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
|
||||
IF_LOOPBACK_REGEX = re.compile(r"^Loopback (\d+)$")
|
||||
|
||||
IF_TYPE_ETH = "ethernet"
|
||||
IF_TYPE_LOOPBACK = "loopback"
|
||||
IF_TYPE_VLAN = "vlan"
|
||||
|
||||
IF_TYPE_MAP = {
|
||||
IF_TYPE_ETH: IF_ETH_REGEX,
|
||||
IF_TYPE_VLAN: IF_VLAN_REGEX,
|
||||
IF_TYPE_LOOPBACK: IF_LOOPBACK_REGEX,
|
||||
}
|
||||
UNSUPPORTED_ATTRS = {
|
||||
IF_TYPE_ETH: (),
|
||||
IF_TYPE_VLAN: ('speed', 'rx_rate', 'tx_rate'),
|
||||
IF_TYPE_LOOPBACK: ('speed', 'mtu', 'rx_rate', 'tx_rate'),
|
||||
}
|
||||
UNSUPPORTED_STATES = {
|
||||
IF_TYPE_ETH: ('absent',),
|
||||
IF_TYPE_VLAN: (),
|
||||
IF_TYPE_LOOPBACK: ('up', 'down'),
|
||||
}
|
||||
|
||||
IF_MODIFIABLE_ATTRS = ('speed', 'description', 'mtu')
|
||||
_interface_type = None
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(type='str'),
|
||||
description=dict(),
|
||||
speed=dict(choices=['1G', '10G', '25G', '40G', '50G', '56G', '100G']),
|
||||
mtu=dict(type='int'),
|
||||
enabled=dict(type='bool'),
|
||||
delay=dict(default=10, type='int'),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent', 'up', 'down']),
|
||||
tx_rate=dict(),
|
||||
rx_rate=dict(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def validate_purge(self, value):
|
||||
if value:
|
||||
self._module.fail_json(
|
||||
msg='Purge is not supported!')
|
||||
|
||||
def validate_duplex(self, value):
|
||||
if value != 'auto':
|
||||
self._module.fail_json(
|
||||
msg='Duplex is not supported!')
|
||||
|
||||
def _get_interface_type(self, if_name):
|
||||
if_type = None
|
||||
if_id = None
|
||||
for interface_type, interface_regex in iteritems(self.IF_TYPE_MAP):
|
||||
match = interface_regex.match(if_name)
|
||||
if match:
|
||||
if_type = interface_type
|
||||
if_id = match.group(1)
|
||||
break
|
||||
return if_type, if_id
|
||||
|
||||
def _set_if_type(self, params):
|
||||
if_name = params['name']
|
||||
if_type, if_id = self._get_interface_type(if_name)
|
||||
if not if_id:
|
||||
self._module.fail_json(
|
||||
msg='unsupported interface: %s' % if_name)
|
||||
params['if_type'] = if_type
|
||||
params['if_id'] = if_id
|
||||
|
||||
def _check_supported_attrs(self, if_obj):
|
||||
unsupported_attrs = self.UNSUPPORTED_ATTRS[self._interface_type]
|
||||
for attr in unsupported_attrs:
|
||||
val = if_obj[attr]
|
||||
if val is not None:
|
||||
self._module.fail_json(
|
||||
msg='attribute %s is not supported for %s interface' % (
|
||||
attr, self._interface_type))
|
||||
req_state = if_obj['state']
|
||||
unsupported_states = self.UNSUPPORTED_STATES[self._interface_type]
|
||||
if req_state in unsupported_states:
|
||||
self._module.fail_json(
|
||||
msg='%s state is not supported for %s interface' % (
|
||||
req_state, self._interface_type))
|
||||
|
||||
def _validate_interface_type(self):
|
||||
for if_obj in self._required_config:
|
||||
if_type = if_obj['if_type']
|
||||
if not self._interface_type:
|
||||
self._interface_type = if_type
|
||||
elif self._interface_type != if_type:
|
||||
self._module.fail_json(
|
||||
msg='Cannot aggreagte interfaces from different types')
|
||||
self._check_supported_attrs(if_obj)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
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]
|
||||
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
self._set_if_type(req_item)
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'description': module_params['description'],
|
||||
'speed': module_params['speed'],
|
||||
'mtu': module_params['mtu'],
|
||||
'state': module_params['state'],
|
||||
'delay': module_params['delay'],
|
||||
'enabled': module_params['enabled'],
|
||||
'tx_rate': module_params['tx_rate'],
|
||||
'rx_rate': module_params['rx_rate'],
|
||||
}
|
||||
|
||||
self.validate_param_values(params)
|
||||
self._set_if_type(params)
|
||||
self._required_config.append(params)
|
||||
self._validate_interface_type()
|
||||
|
||||
@classmethod
|
||||
def get_if_name(cls, item):
|
||||
return cls.get_config_attr(item, "header")
|
||||
|
||||
@classmethod
|
||||
def get_admin_state(cls, item):
|
||||
admin_state = cls.get_config_attr(item, "Admin state")
|
||||
return str(admin_state).lower() == "enabled"
|
||||
|
||||
@classmethod
|
||||
def get_oper_state(cls, item):
|
||||
oper_state = cls.get_config_attr(item, "Operational state")
|
||||
if not oper_state:
|
||||
oper_state = cls.get_config_attr(item, "State")
|
||||
return str(oper_state).lower()
|
||||
|
||||
@classmethod
|
||||
def get_speed(cls, item):
|
||||
speed = cls.get_config_attr(item, 'Actual speed')
|
||||
if not speed:
|
||||
return
|
||||
try:
|
||||
speed = int(speed.split()[0])
|
||||
return "%dG" % speed
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _create_if_data(self, name, item):
|
||||
regex = self.IF_TYPE_MAP[self._interface_type]
|
||||
if_id = ''
|
||||
match = regex.match(name)
|
||||
if match:
|
||||
if_id = match.group(1)
|
||||
return dict(
|
||||
name=name,
|
||||
description=self.get_config_attr(item, 'Description'),
|
||||
speed=self.get_speed(item),
|
||||
mtu=self.get_mtu(item),
|
||||
enabled=self.get_admin_state(item),
|
||||
state=self.get_oper_state(item),
|
||||
if_id=if_id)
|
||||
|
||||
def _get_interfaces_config(self):
|
||||
return get_interfaces_config(self._module, self._interface_type)
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
config = self._get_interfaces_config()
|
||||
if not config:
|
||||
return
|
||||
|
||||
for item in config:
|
||||
name = self.get_if_name(item)
|
||||
self._current_config[name] = self._create_if_data(name, item)
|
||||
|
||||
def _generate_no_if_commands(self, req_if, curr_if):
|
||||
if self._interface_type == self.IF_TYPE_ETH:
|
||||
name = req_if['name']
|
||||
self._module.fail_json(
|
||||
msg='cannot remove ethernet interface %s' % name)
|
||||
if not curr_if:
|
||||
return
|
||||
if_id = req_if['if_id']
|
||||
if not if_id:
|
||||
return
|
||||
self._commands.append(
|
||||
'no interface %s %s' % (self._interface_type, if_id))
|
||||
|
||||
def _add_commands_to_interface(self, req_if, cmd_list):
|
||||
if not cmd_list:
|
||||
return
|
||||
if_id = req_if['if_id']
|
||||
if not if_id:
|
||||
return
|
||||
self._commands.append(
|
||||
'interface %s %s' % (self._interface_type, if_id))
|
||||
self._commands.extend(cmd_list)
|
||||
self._commands.append('exit')
|
||||
|
||||
def _generate_if_commands(self, req_if, curr_if):
|
||||
enabled = req_if['enabled']
|
||||
cmd_list = []
|
||||
for attr_name in self.IF_MODIFIABLE_ATTRS:
|
||||
candidate = req_if.get(attr_name)
|
||||
running = curr_if.get(attr_name)
|
||||
if candidate != running:
|
||||
if candidate:
|
||||
cmd = attr_name + ' ' + str(candidate)
|
||||
if self._interface_type == self.IF_TYPE_ETH and \
|
||||
attr_name in ('mtu', 'speed'):
|
||||
cmd = cmd + ' ' + 'force'
|
||||
cmd_list.append(cmd)
|
||||
curr_enabled = curr_if.get('enabled', False)
|
||||
if enabled is not None and enabled != curr_enabled:
|
||||
cmd = 'shutdown'
|
||||
if enabled:
|
||||
cmd = "no %s" % cmd
|
||||
cmd_list.append(cmd)
|
||||
if cmd_list:
|
||||
self._add_commands_to_interface(req_if, cmd_list)
|
||||
|
||||
def generate_commands(self):
|
||||
for req_if in self._required_config:
|
||||
name = req_if['name']
|
||||
curr_if = self._current_config.get(name, {})
|
||||
if not curr_if and self._interface_type == self.IF_TYPE_ETH:
|
||||
self._module.fail_json(
|
||||
msg='could not find ethernet interface %s' % name)
|
||||
continue
|
||||
req_state = req_if['state']
|
||||
if req_state == 'absent':
|
||||
self._generate_no_if_commands(req_if, curr_if)
|
||||
else:
|
||||
self._generate_if_commands(req_if, curr_if)
|
||||
|
||||
def _get_interfaces_rates(self):
|
||||
return get_interfaces_config(self._module, self._interface_type,
|
||||
"rates")
|
||||
|
||||
def _get_interfaces_status(self):
|
||||
return get_interfaces_config(self._module, self._interface_type,
|
||||
"status")
|
||||
|
||||
def _check_state(self, name, want_state, statuses):
|
||||
curr_if = statuses.get(name, {})
|
||||
if curr_if:
|
||||
curr_if = curr_if[0]
|
||||
curr_state = self.get_oper_state(curr_if).strip()
|
||||
if curr_state is None or not conditional(want_state, curr_state):
|
||||
return 'state eq(%s)' % want_state
|
||||
|
||||
def check_declarative_intent_params(self, result):
|
||||
failed_conditions = []
|
||||
delay_called = False
|
||||
rates = None
|
||||
statuses = None
|
||||
for req_if in self._required_config:
|
||||
want_state = req_if.get('state')
|
||||
want_tx_rate = req_if.get('tx_rate')
|
||||
want_rx_rate = req_if.get('rx_rate')
|
||||
name = req_if['name']
|
||||
if want_state not in ('up', 'down') and not want_tx_rate and not \
|
||||
want_rx_rate:
|
||||
continue
|
||||
if not delay_called and result['changed']:
|
||||
delay_called = True
|
||||
delay = req_if['delay']
|
||||
if delay > 0:
|
||||
sleep(delay)
|
||||
if want_state in ('up', 'down'):
|
||||
if statuses is None:
|
||||
statuses = self._get_interfaces_status() or {}
|
||||
cond = self._check_state(name, want_state, statuses)
|
||||
if cond:
|
||||
failed_conditions.append(cond)
|
||||
if_rates = None
|
||||
if want_tx_rate or want_rx_rate:
|
||||
if not rates:
|
||||
rates = self._get_interfaces_rates()
|
||||
if_rates = rates.get(name)
|
||||
if if_rates:
|
||||
if_rates = if_rates[0]
|
||||
if want_tx_rate:
|
||||
have_tx_rate = None
|
||||
if if_rates:
|
||||
have_tx_rate = if_rates.get('egress rate')
|
||||
if have_tx_rate:
|
||||
have_tx_rate = have_tx_rate.split()[0]
|
||||
if have_tx_rate is None or not \
|
||||
conditional(want_tx_rate, have_tx_rate.strip(),
|
||||
cast=int):
|
||||
failed_conditions.append('tx_rate ' + want_tx_rate)
|
||||
|
||||
if want_rx_rate:
|
||||
have_rx_rate = None
|
||||
if if_rates:
|
||||
have_rx_rate = if_rates.get('ingress rate')
|
||||
if have_rx_rate:
|
||||
have_rx_rate = have_rx_rate.split()[0]
|
||||
if have_rx_rate is None or not \
|
||||
conditional(want_rx_rate, have_rx_rate.strip(),
|
||||
cast=int):
|
||||
failed_conditions.append('rx_rate ' + want_rx_rate)
|
||||
|
||||
return failed_conditions
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxInterfaceModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
284
lib/ansible/modules/network/onyx/onyx_l2_interface.py
Normal file
284
lib/ansible/modules/network/onyx/onyx_l2_interface.py
Normal file
@@ -0,0 +1,284 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_l2_interface
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage Layer-2 interface on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of Layer-2 interface
|
||||
on Mellanox ONYX network devices.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the interface.
|
||||
aggregate:
|
||||
description:
|
||||
- List of Layer-2 interface definitions.
|
||||
mode:
|
||||
description:
|
||||
- Mode in which interface needs to be configured.
|
||||
default: access
|
||||
choices: ['access', 'trunk', 'hybrid']
|
||||
access_vlan:
|
||||
description:
|
||||
- Configure given VLAN in access port.
|
||||
trunk_allowed_vlans:
|
||||
description:
|
||||
- List of allowed VLANs in a given trunk port.
|
||||
state:
|
||||
description:
|
||||
- State of the Layer-2 Interface configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure Layer-2 interface
|
||||
onyx_l2_interface:
|
||||
name: Eth1/1
|
||||
mode: access
|
||||
access_vlan: 30
|
||||
|
||||
- name: remove Layer-2 interface configuration
|
||||
onyx_l2_interface:
|
||||
name: Eth1/1
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- interface ethernet 1/1
|
||||
- switchport mode access
|
||||
- switchport access vlan 30
|
||||
"""
|
||||
from copy import deepcopy
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
|
||||
|
||||
|
||||
class OnyxL2InterfaceModule(BaseOnyxModule):
|
||||
IFNAME_REGEX = re.compile(r"^.*(Eth\d+\/\d+|Mpo\d+|Po\d+)")
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(),
|
||||
access_vlan=dict(type='int'),
|
||||
trunk_allowed_vlans=dict(type='list', elements='int'),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent']),
|
||||
mode=dict(default='access',
|
||||
choices=['access', 'hybrid', 'trunk']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
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]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'access_vlan': module_params['access_vlan'],
|
||||
'trunk_allowed_vlans': module_params['trunk_allowed_vlans'],
|
||||
'mode': module_params['mode'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
def validate_access_vlan(self, value):
|
||||
if value and not 1 <= int(value) <= 4094:
|
||||
self._module.fail_json(msg='vlan id must be between 1 and 4094')
|
||||
|
||||
@classmethod
|
||||
def get_allowed_vlans(cls, if_data):
|
||||
allowed_vlans = cls.get_config_attr(if_data, 'Allowed vlans')
|
||||
if allowed_vlans:
|
||||
vlans = allowed_vlans.split(',')
|
||||
allowed_vlans = [int(vlan.strip()) for vlan in vlans]
|
||||
return allowed_vlans
|
||||
|
||||
@classmethod
|
||||
def get_access_vlan(cls, if_data):
|
||||
access_vlan = cls.get_config_attr(if_data, 'Access vlan')
|
||||
if access_vlan:
|
||||
return int(access_vlan)
|
||||
|
||||
def _create_switchport_data(self, if_name, if_data):
|
||||
return {
|
||||
'name': if_name,
|
||||
'mode': self.get_config_attr(if_data, 'Mode'),
|
||||
'access_vlan': self.get_access_vlan(if_data),
|
||||
'trunk_allowed_vlans': self.get_allowed_vlans(if_data)
|
||||
}
|
||||
|
||||
def _get_switchport_config(self):
|
||||
return get_interfaces_config(self._module, 'switchport')
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
switchports_config = self._get_switchport_config()
|
||||
if not switchports_config:
|
||||
return
|
||||
for if_name, if_data in iteritems(switchports_config):
|
||||
self._current_config[if_name] = \
|
||||
self._create_switchport_data(if_name, if_data)
|
||||
|
||||
def _get_switchport_command_name(self, if_name):
|
||||
if if_name.startswith('Eth'):
|
||||
return if_name.replace("Eth", "ethernet ")
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "port-channel ")
|
||||
if if_name.startswith('Mpo'):
|
||||
return if_name.replace("Mpo", "mlag-port-channel ")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _add_interface_commands(self, if_name, commands):
|
||||
if_cmd_name = self._get_switchport_command_name(if_name)
|
||||
self._commands.append("interface %s" % if_cmd_name)
|
||||
self._commands.extend(commands)
|
||||
self._commands.append('exit')
|
||||
|
||||
def _generate_no_switchport_commands(self, if_name):
|
||||
commands = ['no switchport force']
|
||||
self._add_interface_commands(if_name, commands)
|
||||
|
||||
def _generate_switchport_commands(self, if_name, req_conf):
|
||||
commands = []
|
||||
curr_conf = self._current_config.get(if_name, {})
|
||||
curr_mode = curr_conf.get('mode')
|
||||
req_mode = req_conf.get('mode')
|
||||
if req_mode != curr_mode:
|
||||
commands.append('switchport mode %s' % req_mode)
|
||||
curr_access_vlan = curr_conf.get('access_vlan')
|
||||
req_access_vlan = req_conf.get('access_vlan')
|
||||
if curr_access_vlan != req_access_vlan and req_access_vlan:
|
||||
commands.append('switchport access vlan %s' % req_access_vlan)
|
||||
curr_trunk_vlans = curr_conf.get('trunk_allowed_vlans') or set()
|
||||
if curr_trunk_vlans:
|
||||
curr_trunk_vlans = set(curr_trunk_vlans)
|
||||
req_trunk_vlans = req_conf.get('trunk_allowed_vlans') or set()
|
||||
if req_trunk_vlans:
|
||||
req_trunk_vlans = set(req_trunk_vlans)
|
||||
if req_mode != 'access' and curr_trunk_vlans != req_trunk_vlans:
|
||||
removed_vlans = curr_trunk_vlans - req_trunk_vlans
|
||||
for vlan_id in removed_vlans:
|
||||
commands.append('switchport %s allowed-vlan remove %s' %
|
||||
(req_mode, vlan_id))
|
||||
added_vlans = req_trunk_vlans - curr_trunk_vlans
|
||||
for vlan_id in added_vlans:
|
||||
commands.append('switchport %s allowed-vlan add %s' %
|
||||
(req_mode, vlan_id))
|
||||
|
||||
if commands:
|
||||
self._add_interface_commands(if_name, commands)
|
||||
|
||||
def generate_commands(self):
|
||||
for req_conf in self._required_config:
|
||||
state = req_conf['state']
|
||||
if_name = req_conf['name']
|
||||
if state == 'absent':
|
||||
if if_name in self._current_config:
|
||||
self._generate_no_switchport_commands(if_name)
|
||||
else:
|
||||
self._generate_switchport_commands(if_name, req_conf)
|
||||
|
||||
def _generate_vlan_commands(self, vlan_id, req_conf):
|
||||
curr_vlan = self._current_config.get(vlan_id, {})
|
||||
if not curr_vlan:
|
||||
cmd = "vlan " + vlan_id
|
||||
self._commands.append("vlan %s" % vlan_id)
|
||||
self._commands.append("exit")
|
||||
vlan_name = req_conf['vlan_name']
|
||||
if vlan_name:
|
||||
if vlan_name != curr_vlan.get('vlan_name'):
|
||||
self._commands.append("vlan %s name %s" % (vlan_id, vlan_name))
|
||||
curr_members = set(curr_vlan.get('interfaces', []))
|
||||
req_members = req_conf['interfaces']
|
||||
mode = req_conf['mode']
|
||||
for member in req_members:
|
||||
if member in curr_members:
|
||||
continue
|
||||
if_name = self.get_switchport_command_name(member)
|
||||
cmd = "interface %s switchport mode %s" % (if_name, mode)
|
||||
self._commands.append(cmd)
|
||||
cmd = "interface %s switchport %s allowed-vlan add %s" % (
|
||||
if_name, mode, vlan_id)
|
||||
self._commands.append(cmd)
|
||||
req_members = set(req_members)
|
||||
for member in curr_members:
|
||||
if member in req_members:
|
||||
continue
|
||||
if_name = self.get_switchport_command_name(member)
|
||||
cmd = "interface %s switchport %s allowed-vlan remove %s" % (
|
||||
if_name, mode, vlan_id)
|
||||
self._commands.append(cmd)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxL2InterfaceModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
292
lib/ansible/modules/network/onyx/onyx_l3_interface.py
Normal file
292
lib/ansible/modules/network/onyx/onyx_l3_interface.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_l3_interface
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage L3 interfaces on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of L3 interfaces
|
||||
on Mellanox ONYX network devices.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the L3 interface.
|
||||
ipv4:
|
||||
description:
|
||||
- IPv4 of the L3 interface.
|
||||
ipv6:
|
||||
description:
|
||||
- IPv6 of the L3 interface (not supported for now).
|
||||
aggregate:
|
||||
description: List of L3 interfaces definitions
|
||||
purge:
|
||||
description:
|
||||
- Purge L3 interfaces not defined in the I(aggregate) parameter.
|
||||
default: false
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- State of the L3 interface configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Set Eth1/1 IPv4 address
|
||||
onyx_l3_interface:
|
||||
name: Eth1/1
|
||||
ipv4: 192.168.0.1/24
|
||||
|
||||
- name: Remove Eth1/1 IPv4 address
|
||||
onyx_l3_interface:
|
||||
name: Eth1/1
|
||||
state: absent
|
||||
|
||||
- name: Set IP addresses on aggregate
|
||||
onyx_l3_interface:
|
||||
aggregate:
|
||||
- { name: Eth1/1, ipv4: 192.168.2.10/24 }
|
||||
- { name: Eth1/2, ipv4: 192.168.3.10/24 }
|
||||
|
||||
- name: Remove IP addresses on aggregate
|
||||
onyx_l3_interface:
|
||||
aggregate:
|
||||
- { name: Eth1/1, ipv4: 192.168.2.10/24 }
|
||||
- { name: Eth1/2, ipv4: 192.168.3.10/24 }
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- interfaces ethernet 1/1 ip address 192.168.0.1 /24
|
||||
"""
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
|
||||
|
||||
|
||||
class OnyxL3InterfaceModule(BaseOnyxModule):
|
||||
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
|
||||
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
|
||||
IF_LOOPBACK_REGEX = re.compile(r"^Loopback (\d+)$")
|
||||
|
||||
IF_TYPE_ETH = "ethernet"
|
||||
IF_TYPE_LOOPBACK = "loopback"
|
||||
IF_TYPE_VLAN = "vlan"
|
||||
|
||||
IF_TYPE_MAP = {
|
||||
IF_TYPE_ETH: IF_ETH_REGEX,
|
||||
IF_TYPE_VLAN: IF_VLAN_REGEX,
|
||||
IF_TYPE_LOOPBACK: IF_LOOPBACK_REGEX,
|
||||
}
|
||||
|
||||
IP_ADDR_ATTR_MAP = {
|
||||
IF_TYPE_ETH: 'IP Address',
|
||||
IF_TYPE_VLAN: 'Internet Address',
|
||||
IF_TYPE_LOOPBACK: 'Internet Address',
|
||||
}
|
||||
|
||||
_purge = False
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(type='str'),
|
||||
ipv4=dict(type='str'),
|
||||
ipv6=dict(type='str'),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent', 'enabled', 'disabled']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def _get_interface_type(self, if_name):
|
||||
if_type = None
|
||||
if_id = None
|
||||
for interface_type, interface_regex in iteritems(self.IF_TYPE_MAP):
|
||||
match = interface_regex.match(if_name)
|
||||
if match:
|
||||
if_type = interface_type
|
||||
if_id = match.group(1)
|
||||
break
|
||||
return if_type, if_id
|
||||
|
||||
def _set_if_type(self, params):
|
||||
if_name = params['name']
|
||||
if_type, if_id = self._get_interface_type(if_name)
|
||||
if not if_id:
|
||||
self._module.fail_json(
|
||||
msg='unsupported interface: %s' % if_name)
|
||||
params['if_type'] = if_type
|
||||
params['if_id'] = if_id
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
aggregate = module_params.get('aggregate')
|
||||
self._purge = module_params.get('purge', False)
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module_params[key]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
self._set_if_type(req_item)
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'ipv4': module_params['ipv4'],
|
||||
'ipv6': module_params['ipv6'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._set_if_type(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
def _get_interfaces_config(self, interface_type):
|
||||
return get_interfaces_config(self._module, interface_type)
|
||||
|
||||
def _parse_interfaces_config(self, if_type, if_config):
|
||||
ipaddr_attr = self.IP_ADDR_ATTR_MAP[if_type]
|
||||
for if_data in if_config:
|
||||
if_name = self.get_config_attr(if_data, 'header')
|
||||
regex = self.IF_TYPE_MAP[if_type]
|
||||
match = regex.match(if_name)
|
||||
if not match:
|
||||
continue
|
||||
ipv4 = self.get_config_attr(if_data, ipaddr_attr)
|
||||
if ipv4:
|
||||
ipv4 = ipv4.replace(' ', '')
|
||||
ipv6 = self.get_config_attr(if_data, 'IPv6 address(es)')
|
||||
if ipv6:
|
||||
ipv6 = ipv6.replace('[primary]', '')
|
||||
ipv6 = ipv6.strip()
|
||||
if_id = match.group(1)
|
||||
switchport = self.get_config_attr(if_data, 'Switchport mode')
|
||||
if_obj = {
|
||||
'name': if_name,
|
||||
'if_id': if_id,
|
||||
'if_type': if_type,
|
||||
'ipv4': ipv4,
|
||||
'ipv6': ipv6,
|
||||
'switchport': switchport,
|
||||
}
|
||||
self._current_config[if_name] = if_obj
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
if_types = set([if_obj['if_type'] for if_obj in self._required_config])
|
||||
for if_type in if_types:
|
||||
if_config = self._get_interfaces_config(if_type)
|
||||
if not if_config:
|
||||
continue
|
||||
self._parse_interfaces_config(if_type, if_config)
|
||||
|
||||
def _generate_no_ip_commands(self, req_conf, curr_conf):
|
||||
curr_ip = curr_conf.get('ipv4')
|
||||
if_type = req_conf['if_type']
|
||||
if_id = req_conf['if_id']
|
||||
if curr_ip:
|
||||
cmd = "interface %s %s no ip address" % (if_type, if_id)
|
||||
self._commands.append(cmd)
|
||||
curr_ipv6 = curr_conf.get('ipv6')
|
||||
if curr_ipv6:
|
||||
cmd = "interface %s %s no ipv6 address %s" % (
|
||||
if_type, if_id, curr_ipv6)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def _generate_ip_commands(self, req_conf, curr_conf):
|
||||
curr_ipv4 = curr_conf.get('ipv4')
|
||||
req_ipv4 = req_conf.get('ipv4')
|
||||
curr_ipv6 = curr_conf.get('ipv6')
|
||||
req_ipv6 = req_conf.get('ipv6')
|
||||
if_type = req_conf['if_type']
|
||||
if_id = req_conf['if_id']
|
||||
switchport = curr_conf.get('switchport')
|
||||
if switchport:
|
||||
cmd = "interface %s %s no switchport force" % (if_type, if_id)
|
||||
self._commands.append(cmd)
|
||||
if curr_ipv4 != req_ipv4:
|
||||
cmd = "interface %s %s ip address %s" % (if_type, if_id, req_ipv4)
|
||||
self._commands.append(cmd)
|
||||
if curr_ipv6 != req_ipv6:
|
||||
cmd = "interface %s %s ipv6 address %s" % (
|
||||
if_type, if_id, req_ipv6)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def generate_commands(self):
|
||||
req_interfaces = set()
|
||||
for req_conf in self._required_config:
|
||||
state = req_conf['state']
|
||||
if_name = req_conf['name']
|
||||
curr_conf = self._current_config.get(if_name, {})
|
||||
if state == 'absent':
|
||||
self._generate_no_ip_commands(req_conf, curr_conf)
|
||||
else:
|
||||
req_interfaces.add(if_name)
|
||||
self._generate_ip_commands(req_conf, curr_conf)
|
||||
if self._purge:
|
||||
for if_name, curr_conf in iteritems(self._current_config):
|
||||
if if_name not in req_interfaces:
|
||||
self._generate_no_ip_commands(req_conf, curr_conf)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxL3InterfaceModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
341
lib/ansible/modules/network/onyx/onyx_linkagg.py
Normal file
341
lib/ansible/modules/network/onyx/onyx_linkagg.py
Normal file
@@ -0,0 +1,341 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_linkagg
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage link aggregation groups on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of link aggregation groups
|
||||
on Mellanox ONYX network devices.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the link aggregation group.
|
||||
required: true
|
||||
mode:
|
||||
description:
|
||||
- Mode of the link aggregation group. A value of C(on) will enable LACP.
|
||||
C(active) configures the link to actively information about the state of the link,
|
||||
or it can be configured in C(passive) mode ie. send link state information only when
|
||||
received them from another link.
|
||||
default: on
|
||||
choices: ['on', 'active', 'passive']
|
||||
members:
|
||||
description:
|
||||
- List of members interfaces of the link aggregation group. The value can be
|
||||
single interface or list of interfaces.
|
||||
required: true
|
||||
aggregate:
|
||||
description: List of link aggregation definitions.
|
||||
purge:
|
||||
description:
|
||||
- Purge link aggregation groups not defined in the I(aggregate) parameter.
|
||||
default: false
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- State of the link aggregation group.
|
||||
default: present
|
||||
choices: ['present', 'absent', 'up', 'down']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure link aggregation group
|
||||
onyx_linkagg:
|
||||
name: Po1
|
||||
members:
|
||||
- Eth1/1
|
||||
- Eth1/2
|
||||
|
||||
- name: remove configuration
|
||||
onyx_linkagg:
|
||||
name: Po1
|
||||
state: absent
|
||||
|
||||
- name: Create aggregate of linkagg definitions
|
||||
onyx_linkagg:
|
||||
aggregate:
|
||||
- { name: Po1, members: [Eth1/1] }
|
||||
- { name: Po2, members: [Eth1/2] }
|
||||
|
||||
- name: Remove aggregate of linkagg definitions
|
||||
onyx_linkagg:
|
||||
aggregate:
|
||||
- name: Po1
|
||||
- name: Po2
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- interface port-channel 1
|
||||
- exit
|
||||
- interface ethernet 1/1 channel-group 1 mode on
|
||||
- interface ethernet 1/2 channel-group 1 mode on
|
||||
"""
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
|
||||
|
||||
|
||||
class OnyxLinkAggModule(BaseOnyxModule):
|
||||
LAG_ID_REGEX = re.compile(r"^\d+ (Po\d+|Mpo\d+)\(([A-Z])\)$")
|
||||
LAG_NAME_REGEX = re.compile(r"^(Po|Mpo)(\d+)$")
|
||||
IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\/\d+)(.*)$")
|
||||
PORT_CHANNEL = 'port-channel'
|
||||
CHANNEL_GROUP = 'channel-group'
|
||||
MLAG_PORT_CHANNEL = 'mlag-port-channel'
|
||||
MLAG_CHANNEL_GROUP = 'mlag-channel-group'
|
||||
|
||||
LAG_TYPE = 'lag'
|
||||
MLAG_TYPE = 'mlag'
|
||||
|
||||
IF_TYPE_MAP = dict(
|
||||
lag=PORT_CHANNEL,
|
||||
mlag=MLAG_PORT_CHANNEL
|
||||
)
|
||||
|
||||
_purge = False
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(type='str'),
|
||||
members=dict(type='list'),
|
||||
mode=dict(default='on', choices=['active', 'on', 'passive']),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def _get_lag_type(self, lag_name):
|
||||
match = self.LAG_NAME_REGEX.match(lag_name)
|
||||
if match:
|
||||
prefix = match.group(1)
|
||||
if prefix == "Po":
|
||||
return self.LAG_TYPE
|
||||
return self.MLAG_TYPE
|
||||
self._module.fail_json(
|
||||
msg='invalid lag name: %s, lag name should start with Po or '
|
||||
'Mpo' % lag_name)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
aggregate = module_params.get('aggregate')
|
||||
self._purge = module_params.get('purge', False)
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module_params[key]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
req_item['type'] = self._get_lag_type(req_item['name'])
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'state': module_params['state'],
|
||||
'members': module_params['members'],
|
||||
'mode': module_params['mode'],
|
||||
'type': self._get_lag_type(module_params['name']),
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
@classmethod
|
||||
def _extract_lag_name(cls, header):
|
||||
match = cls.LAG_ID_REGEX.match(header)
|
||||
state = None
|
||||
lag_name = None
|
||||
if match:
|
||||
state = 'up' if match.group(2) == 'U' else 'down'
|
||||
lag_name = match.group(1)
|
||||
return lag_name, state
|
||||
|
||||
@classmethod
|
||||
def _extract_if_name(cls, member):
|
||||
match = cls.IF_NAME_REGEX.match(member)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
@classmethod
|
||||
def _extract_lag_members(cls, lag_type, lag_item):
|
||||
members = ""
|
||||
if lag_type == cls.LAG_TYPE:
|
||||
members = cls.get_config_attr(lag_item, "Member Ports")
|
||||
else:
|
||||
for attr_name, attr_val in iteritems(lag_item):
|
||||
if attr_name.startswith('Local Ports'):
|
||||
members = attr_val
|
||||
return [cls._extract_if_name(member) for member in members.split()]
|
||||
|
||||
def _get_port_channels(self, if_type):
|
||||
return get_interfaces_config(self._module, if_type, flags="summary")
|
||||
|
||||
def _parse_port_channels_summary(self, lag_type, lag_summary):
|
||||
if lag_type == self.MLAG_TYPE:
|
||||
lag_summary = lag_summary.get('MLAG Port-Channel Summary', {})
|
||||
for lag_key, lag_data in iteritems(lag_summary):
|
||||
lag_name, state = self._extract_lag_name(lag_key)
|
||||
if not lag_name:
|
||||
continue
|
||||
lag_members = self._extract_lag_members(lag_type, lag_data[0])
|
||||
lag_obj = dict(
|
||||
name=lag_name,
|
||||
state=state,
|
||||
members=lag_members
|
||||
)
|
||||
self._current_config[lag_name] = lag_obj
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
lag_types = set([lag_obj['type'] for lag_obj in self._required_config])
|
||||
for lag_type in lag_types:
|
||||
if_type = self.IF_TYPE_MAP[lag_type]
|
||||
lag_summary = self._get_port_channels(if_type)
|
||||
if lag_summary:
|
||||
self._parse_port_channels_summary(lag_type, lag_summary)
|
||||
with open('/tmp/linagg.txt', 'w') as fp:
|
||||
fp.write('current_config: %s\n' % self._current_config)
|
||||
fp.write('required_config: %s\n' % self._required_config)
|
||||
|
||||
def _get_interface_command_suffix(self, if_name):
|
||||
if if_name.startswith('Eth'):
|
||||
return if_name.replace("Eth", "ethernet ")
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "port-channel ")
|
||||
if if_name.startswith('Mpo'):
|
||||
return if_name.replace("Mpo", "mlag-port-channel ")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _get_channel_group(self, if_name):
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "channel-group ")
|
||||
if if_name.startswith('Mpo'):
|
||||
return if_name.replace("Mpo", "mlag-channel-group ")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _generate_no_linkagg_commands(self, lag_name):
|
||||
suffix = self._get_interface_command_suffix(lag_name)
|
||||
command = 'no interface %s' % suffix
|
||||
self._commands.append(command)
|
||||
|
||||
def _generate_linkagg_commands(self, lag_name, req_lag):
|
||||
curr_lag = self._current_config.get(lag_name, {})
|
||||
if not curr_lag:
|
||||
suffix = self._get_interface_command_suffix(lag_name)
|
||||
self._commands.append("interface %s" % suffix)
|
||||
self._commands.append("exit")
|
||||
curr_members = set(curr_lag.get('members', []))
|
||||
req_members = set(req_lag.get('members') or [])
|
||||
|
||||
lag_mode = req_lag['mode']
|
||||
if req_members != curr_members:
|
||||
channel_group = self._get_channel_group(lag_name)
|
||||
channel_group_type = channel_group.split()[0]
|
||||
for member in req_members:
|
||||
if member in curr_members:
|
||||
continue
|
||||
suffix = self._get_interface_command_suffix(member)
|
||||
self._commands.append(
|
||||
"interface %s %s mode %s" %
|
||||
(suffix, channel_group, lag_mode))
|
||||
for member in curr_members:
|
||||
if member in req_members:
|
||||
continue
|
||||
suffix = self._get_interface_command_suffix(member)
|
||||
self._commands.append(
|
||||
"interface %s no %s" % (suffix, channel_group_type))
|
||||
req_state = req_lag.get('state')
|
||||
if req_state in ('up', 'down'):
|
||||
curr_state = curr_lag.get('state')
|
||||
if curr_state != req_state:
|
||||
suffix = self._get_interface_command_suffix(lag_name)
|
||||
cmd = "interface %s " % suffix
|
||||
if req_state == 'up':
|
||||
cmd += 'no shutdown'
|
||||
else:
|
||||
cmd += 'shutdown'
|
||||
self._commands.append(cmd)
|
||||
|
||||
def generate_commands(self):
|
||||
req_lags = set()
|
||||
for req_conf in self._required_config:
|
||||
state = req_conf['state']
|
||||
lag_name = req_conf['name']
|
||||
if state == 'absent':
|
||||
if lag_name in self._current_config:
|
||||
self._generate_no_linkagg_commands(lag_name)
|
||||
else:
|
||||
req_lags.add(lag_name)
|
||||
self._generate_linkagg_commands(lag_name, req_conf)
|
||||
if self._purge:
|
||||
for lag_name in self._current_config:
|
||||
if lag_name not in req_lags:
|
||||
self._generate_no_linkagg_commands(lag_name)
|
||||
|
||||
def check_declarative_intent_params(self, result):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxLinkAggModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
117
lib/ansible/modules/network/onyx/onyx_lldp.py
Normal file
117
lib/ansible/modules/network/onyx/onyx_lldp.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_lldp
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage LLDP configuration on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of LLDP service configuration
|
||||
on Mellanox ONYX network devices.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- State of the LLDP protocol configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Enable LLDP protocol
|
||||
onyx_lldp:
|
||||
state: present
|
||||
|
||||
- name: Disable LLDP protocol
|
||||
onyx_lldp:
|
||||
state: lldp
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- lldp
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxLldpModule(BaseOnyxModule):
|
||||
LLDP_ENTRY = 'LLDP'
|
||||
SHOW_LLDP_CMD = 'show lldp local'
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
argument_spec = dict()
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = dict()
|
||||
module_params = self._module.params
|
||||
params = {
|
||||
'state': module_params['state'],
|
||||
}
|
||||
|
||||
self.validate_param_values(params)
|
||||
self._required_config.update(params)
|
||||
|
||||
def _get_lldp_config(self):
|
||||
return show_cmd(self._module, self.SHOW_LLDP_CMD)
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
state = 'absent'
|
||||
config = self._get_lldp_config() or dict()
|
||||
for item in config:
|
||||
lldp_state = item.get(self.LLDP_ENTRY)
|
||||
if lldp_state is not None:
|
||||
if lldp_state == 'enabled':
|
||||
state = 'present'
|
||||
break
|
||||
self._current_config['state'] = state
|
||||
|
||||
def generate_commands(self):
|
||||
req_state = self._required_config['state']
|
||||
curr_state = self._current_config['state']
|
||||
if curr_state != req_state:
|
||||
cmd = 'lldp'
|
||||
if req_state == 'absent':
|
||||
cmd = 'no %s' % cmd
|
||||
self._commands.append(cmd)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxLldpModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
229
lib/ansible/modules/network/onyx/onyx_lldp_interface.py
Normal file
229
lib/ansible/modules/network/onyx/onyx_lldp_interface.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_lldp_interface
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage LLDP interfaces configuration on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of LLDP interfaces
|
||||
configuration on Mellanox ONYX network devices.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the interface LLDP should be configured on.
|
||||
aggregate:
|
||||
description: List of interfaces LLDP should be configured on.
|
||||
purge:
|
||||
description:
|
||||
- Purge interfaces not defined in the aggregate parameter.
|
||||
type: bool
|
||||
default: false
|
||||
state:
|
||||
description:
|
||||
- State of the LLDP configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent', 'enabled', 'disabled']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Configure LLDP on specific interfaces
|
||||
onyx_lldp_interface:
|
||||
name: Eth1/1
|
||||
state: present
|
||||
|
||||
- name: Disable LLDP on specific interfaces
|
||||
onyx_lldp_interface:
|
||||
name: Eth1/1
|
||||
state: disabled
|
||||
|
||||
- name: Enable LLDP on specific interfaces
|
||||
onyx_lldp_interface:
|
||||
name: Eth1/1
|
||||
state: enabled
|
||||
|
||||
- name: Delete LLDP on specific interfaces
|
||||
onyx_lldp_interface:
|
||||
name: Eth1/1
|
||||
state: absent
|
||||
|
||||
- name: Create aggregate of LLDP interface configurations
|
||||
onyx_lldp_interface:
|
||||
aggregate:
|
||||
- { name: Eth1/1 }
|
||||
- { name: Eth1/2 }
|
||||
state: present
|
||||
|
||||
- name: Delete aggregate of LLDP interface configurations
|
||||
onyx_lldp_interface:
|
||||
aggregate:
|
||||
- { name: Eth1/1 }
|
||||
- { name: Eth1/2 }
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- interface ethernet 1/1 lldp transmit
|
||||
- interface ethernet 1/1 lldp receive
|
||||
"""
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxLldpInterfaceModule(BaseOnyxModule):
|
||||
IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\d+)$")
|
||||
_purge = False
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(type='str'),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent', 'enabled', 'disabled']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
aggregate = module_params.get('aggregate')
|
||||
self._purge = module_params.get('purge', False)
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module_params[key]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
def _create_if_lldp_data(self, if_name, if_lldp_data):
|
||||
return {
|
||||
'name': if_name,
|
||||
'receive': self.get_config_attr(if_lldp_data, 'Receive'),
|
||||
'transmit': self.get_config_attr(if_lldp_data, 'Transmit'),
|
||||
}
|
||||
|
||||
def _get_lldp_config(self):
|
||||
return show_cmd(self._module, "show lldp interfaces")
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
lldp_config = self._get_lldp_config()
|
||||
if not lldp_config:
|
||||
return
|
||||
for if_name, if_lldp_data in iteritems(lldp_config):
|
||||
match = self.IF_NAME_REGEX.match(if_name)
|
||||
if not match:
|
||||
continue
|
||||
if if_lldp_data:
|
||||
if_lldp_data = if_lldp_data[0]
|
||||
self._current_config[if_name] = \
|
||||
self._create_if_lldp_data(if_name, if_lldp_data)
|
||||
|
||||
def _get_interface_cmd_name(self, if_name):
|
||||
return if_name.replace("Eth", "ethernet ")
|
||||
|
||||
def _add_if_lldp_commands(self, if_name, flag, enable):
|
||||
cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
|
||||
lldp_cmd = "lldp %s" % flag
|
||||
if not enable:
|
||||
lldp_cmd = 'no %s' % lldp_cmd
|
||||
self._commands.append(cmd_prefix + lldp_cmd)
|
||||
|
||||
def _gen_lldp_commands(self, if_name, req_state, curr_conf):
|
||||
curr_receive = curr_conf.get('receive')
|
||||
curr_transmit = curr_conf.get('transmit')
|
||||
enable = (req_state == 'Enabled')
|
||||
if curr_receive != req_state:
|
||||
flag = 'receive'
|
||||
self._add_if_lldp_commands(if_name, flag, enable)
|
||||
if curr_transmit != req_state:
|
||||
flag = 'transmit'
|
||||
self._add_if_lldp_commands(if_name, flag, enable)
|
||||
|
||||
def generate_commands(self):
|
||||
req_interfaces = set()
|
||||
for req_conf in self._required_config:
|
||||
state = req_conf['state']
|
||||
if_name = req_conf['name']
|
||||
if state in ('absent', 'disabled'):
|
||||
req_state = 'Disabled'
|
||||
else:
|
||||
req_interfaces.add(if_name)
|
||||
req_state = 'Enabled'
|
||||
curr_conf = self._current_config.get(if_name, {})
|
||||
self._gen_lldp_commands(if_name, req_state, curr_conf)
|
||||
if self._purge:
|
||||
for if_name, curr_conf in iteritems(self._current_config):
|
||||
if if_name not in req_interfaces:
|
||||
req_state = 'Disabled'
|
||||
self._gen_lldp_commands(if_name, req_state, curr_conf)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxLldpInterfaceModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
227
lib/ansible/modules/network/onyx/onyx_magp.py
Normal file
227
lib/ansible/modules/network/onyx/onyx_magp.py
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_magp
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage MAGP protocol on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of MAGP protocol on vlan
|
||||
interface of Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
magp_id:
|
||||
description:
|
||||
- "MAGP instance number 1-255"
|
||||
required: true
|
||||
interface:
|
||||
description:
|
||||
- VLAN Interface name.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- MAGP state.
|
||||
default: present
|
||||
choices: ['present', 'absent', 'enabled', 'disabled']
|
||||
router_ip:
|
||||
description:
|
||||
- MAGP router IP address.
|
||||
router_mac:
|
||||
description:
|
||||
- MAGP router MAC address.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: run add vlan interface with magp
|
||||
onyx_magp:
|
||||
magp_id: 103
|
||||
router_ip: 192.168.8.2
|
||||
router_mac: AA:1B:2C:3D:4E:5F
|
||||
interface: Vlan 1002
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- interface vlan 234 magp 103
|
||||
- exit
|
||||
- interface vlan 234 magp 103 ip virtual-router address 1.2.3.4
|
||||
"""
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxMagpModule(BaseOnyxModule):
|
||||
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
magp_id=dict(type='int', required=True),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent', 'enabled', 'disabled']),
|
||||
interface=dict(required=True),
|
||||
router_ip=dict(),
|
||||
router_mac=dict(),
|
||||
)
|
||||
|
||||
def init_module(self):
|
||||
""" Ansible module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
argument_spec = dict()
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def validate_magp_id(self, value):
|
||||
if value and not 1 <= int(value) <= 255:
|
||||
self._module.fail_json(msg='magp id must be between 1 and 255')
|
||||
|
||||
def get_required_config(self):
|
||||
module_params = self._module.params
|
||||
interface = module_params['interface']
|
||||
match = self.IF_VLAN_REGEX.match(interface)
|
||||
vlan_id = 0
|
||||
if match:
|
||||
vlan_id = int(match.group(1))
|
||||
else:
|
||||
self._module.fail_json(
|
||||
msg='Invalid interface name: should be "Vlan <vlan_id>"')
|
||||
|
||||
self._required_config = dict(
|
||||
magp_id=module_params['magp_id'],
|
||||
state=module_params['state'],
|
||||
vlan_id=vlan_id,
|
||||
router_ip=module_params['router_ip'],
|
||||
router_mac=module_params['router_mac'])
|
||||
self.validate_param_values(self._required_config)
|
||||
|
||||
@classmethod
|
||||
def get_magp_id(cls, item):
|
||||
header = cls.get_config_attr(item, "header")
|
||||
return int(header.split()[1])
|
||||
|
||||
def _create_magp_instance_data(self, magp_id, item):
|
||||
vlan_id = int(self.get_config_attr(item, "Interface vlan"))
|
||||
state = self.get_config_attr(item, "Admin state").lower()
|
||||
return dict(
|
||||
magp_id=magp_id,
|
||||
state=state,
|
||||
vlan_id=vlan_id,
|
||||
router_ip=self.get_config_attr(item, "Virtual IP"),
|
||||
router_mac=self.get_config_attr(item, "Virtual MAC"))
|
||||
|
||||
def _update_magp_data(self, magp_data):
|
||||
for magp_item in magp_data:
|
||||
magp_id = self.get_magp_id(magp_item)
|
||||
inst_data = self._create_magp_instance_data(magp_id, magp_item)
|
||||
self._current_config[magp_id] = inst_data
|
||||
|
||||
def _get_magp_config(self):
|
||||
cmd = "show magp"
|
||||
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
magp_data = self._get_magp_config()
|
||||
if magp_data:
|
||||
self._update_magp_data(magp_data)
|
||||
|
||||
def _generate_no_magp_commands(self):
|
||||
req_vlan_id = self._required_config['vlan_id']
|
||||
req_magp_id = self._required_config['magp_id']
|
||||
curr_magp_data = self._current_config.get(req_magp_id)
|
||||
if not curr_magp_data:
|
||||
return
|
||||
curr_vlan_id = curr_magp_data.get(req_vlan_id)
|
||||
if curr_vlan_id == req_vlan_id:
|
||||
cmd = 'interface vlan %s no magp %s' % (req_vlan_id, req_magp_id)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def _generate_magp_commands(self, req_state):
|
||||
req_vlan_id = self._required_config['vlan_id']
|
||||
req_magp_id = self._required_config['magp_id']
|
||||
curr_magp_data = self._current_config.get(req_magp_id, dict())
|
||||
curr_vlan_id = curr_magp_data.get('vlan_id')
|
||||
magp_prefix = 'interface vlan %s magp %s' % (req_vlan_id, req_magp_id)
|
||||
create_new_magp = False
|
||||
if curr_vlan_id != req_vlan_id:
|
||||
if curr_vlan_id:
|
||||
cmd = 'interface vlan %s no magp %s' % (
|
||||
curr_vlan_id, req_magp_id)
|
||||
self._commands.append(cmd)
|
||||
create_new_magp = True
|
||||
self._commands.append(magp_prefix)
|
||||
self._commands.append('exit')
|
||||
req_router_ip = self._required_config['router_ip']
|
||||
curr_router_ip = curr_magp_data.get('router_ip')
|
||||
if req_router_ip:
|
||||
if curr_router_ip != req_router_ip or create_new_magp:
|
||||
cmd = '%s ip virtual-router address %s' % (
|
||||
magp_prefix, req_router_ip)
|
||||
self._commands.append(cmd)
|
||||
else:
|
||||
if curr_router_ip and curr_router_ip != '0.0.0.0':
|
||||
cmd = '%s no ip virtual-router address' % magp_prefix
|
||||
self._commands.append(cmd)
|
||||
req_router_mac = self._required_config['router_mac']
|
||||
curr_router_mac = curr_magp_data.get('router_mac')
|
||||
if curr_router_mac:
|
||||
curr_router_mac = curr_router_mac.lower()
|
||||
if req_router_mac:
|
||||
req_router_mac = req_router_mac.lower()
|
||||
if curr_router_mac != req_router_mac or create_new_magp:
|
||||
cmd = '%s ip virtual-router mac-address %s' % (
|
||||
magp_prefix, req_router_mac)
|
||||
self._commands.append(cmd)
|
||||
else:
|
||||
if curr_router_mac and curr_router_mac != '00:00:00:00:00:00':
|
||||
cmd = '%s no ip virtual-router mac-address' % magp_prefix
|
||||
self._commands.append(cmd)
|
||||
if req_state in ('enabled', 'disabled'):
|
||||
curr_state = curr_magp_data.get('state', 'enabled')
|
||||
if curr_state != req_state:
|
||||
if req_state == 'enabled':
|
||||
suffix = 'no shutdown'
|
||||
else:
|
||||
suffix = 'shutdown'
|
||||
cmd = '%s %s' % (magp_prefix, suffix)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def generate_commands(self):
|
||||
req_state = self._required_config['state']
|
||||
if req_state == 'absent':
|
||||
return self._generate_no_magp_commands()
|
||||
return self._generate_magp_commands(req_state)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxMagpModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
210
lib/ansible/modules/network/onyx/onyx_mlag_ipl.py
Normal file
210
lib/ansible/modules/network/onyx/onyx_mlag_ipl.py
Normal file
@@ -0,0 +1,210 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_mlag_ipl
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage IPL (inter-peer link) on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of IPL (inter-peer link)
|
||||
management on Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the interface (port-channel) IPL should be configured on.
|
||||
required: true
|
||||
vlan_interface:
|
||||
description:
|
||||
- Name of the IPL vlan interface.
|
||||
state:
|
||||
description:
|
||||
- IPL state.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
peer_address:
|
||||
description:
|
||||
- IPL peer IP address.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: run configure ipl
|
||||
onyx_mlag_ipl:
|
||||
name: Po1
|
||||
vlan_interface: Vlan 322
|
||||
state: present
|
||||
peer_address: 192.168.7.1
|
||||
|
||||
- name: run remove ipl
|
||||
onyx_mlag_ipl:
|
||||
name: Po1
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- interface port-channel 1 ipl 1
|
||||
- interface vlan 1024 ipl 1 peer-address 10.10.10.10
|
||||
"""
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxMlagIplModule(BaseOnyxModule):
|
||||
VLAN_IF_REGEX = re.compile(r'^Vlan \d+')
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(required=True),
|
||||
state=dict(default='present',
|
||||
choices=['present', 'absent']),
|
||||
peer_address=dict(),
|
||||
vlan_interface=dict(),
|
||||
)
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
argument_spec = dict()
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
module_params = self._module.params
|
||||
self._required_config = dict(
|
||||
name=module_params['name'],
|
||||
state=module_params['state'],
|
||||
peer_address=module_params['peer_address'],
|
||||
vlan_interface=module_params['vlan_interface'])
|
||||
self.validate_param_values(self._required_config)
|
||||
|
||||
def _update_mlag_data(self, mlag_data):
|
||||
if not mlag_data:
|
||||
return
|
||||
mlag_summary = mlag_data.get("MLAG IPLs Summary", {})
|
||||
ipl_id = "1"
|
||||
ipl_list = mlag_summary.get(ipl_id)
|
||||
if ipl_list:
|
||||
ipl_data = ipl_list[0]
|
||||
vlan_id = ipl_data.get("Vlan Interface")
|
||||
vlan_interface = ""
|
||||
if vlan_id != "N/A":
|
||||
vlan_interface = "Vlan %s" % vlan_id
|
||||
peer_address = ipl_data.get("Peer IP address")
|
||||
name = ipl_data.get("Group Port-Channel")
|
||||
self._current_config = dict(
|
||||
name=name,
|
||||
peer_address=peer_address,
|
||||
vlan_interface=vlan_interface)
|
||||
|
||||
def _show_mlag_data(self):
|
||||
cmd = "show mlag"
|
||||
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
mlag_data = self._show_mlag_data()
|
||||
self._update_mlag_data(mlag_data)
|
||||
|
||||
def _get_interface_cmd_name(self, if_name):
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "port-channel ")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _generate_port_channel_command(self, if_name, enable):
|
||||
if_cmd_name = self._get_interface_cmd_name(if_name)
|
||||
if enable:
|
||||
ipl_cmd = 'ipl 1'
|
||||
else:
|
||||
ipl_cmd = "no ipl 1"
|
||||
cmd = "interface %s %s" % (if_cmd_name, ipl_cmd)
|
||||
return cmd
|
||||
|
||||
def _generate_vlan_if_command(self, if_name, enable, peer_address):
|
||||
if_cmd_name = if_name.lower()
|
||||
if enable:
|
||||
ipl_cmd = 'ipl 1 peer-address %s' % peer_address
|
||||
else:
|
||||
ipl_cmd = "no ipl 1"
|
||||
cmd = "interface %s %s" % (if_cmd_name, ipl_cmd)
|
||||
return cmd
|
||||
|
||||
def _generate_no_ipl_commands(self):
|
||||
curr_interface = self._current_config.get('name')
|
||||
req_interface = self._required_config.get('name')
|
||||
if curr_interface == req_interface:
|
||||
cmd = self._generate_port_channel_command(
|
||||
req_interface, enable=False)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def _generate_ipl_commands(self):
|
||||
curr_interface = self._current_config.get('name')
|
||||
req_interface = self._required_config.get('name')
|
||||
if curr_interface != req_interface:
|
||||
if curr_interface and curr_interface != 'N/A':
|
||||
cmd = self._generate_port_channel_command(
|
||||
curr_interface, enable=False)
|
||||
self._commands.append(cmd)
|
||||
cmd = self._generate_port_channel_command(
|
||||
req_interface, enable=True)
|
||||
self._commands.append(cmd)
|
||||
curr_vlan = self._current_config.get('vlan_interface')
|
||||
req_vlan = self._required_config.get('vlan_interface')
|
||||
add_peer = False
|
||||
if curr_vlan != req_vlan:
|
||||
add_peer = True
|
||||
if curr_vlan:
|
||||
cmd = self._generate_vlan_if_command(curr_vlan, enable=False,
|
||||
peer_address=None)
|
||||
self._commands.append(cmd)
|
||||
curr_peer = self._current_config.get('peer_address')
|
||||
req_peer = self._required_config.get('peer_address')
|
||||
if req_peer != curr_peer:
|
||||
add_peer = True
|
||||
if add_peer and req_peer:
|
||||
cmd = self._generate_vlan_if_command(req_vlan, enable=True,
|
||||
peer_address=req_peer)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def generate_commands(self):
|
||||
state = self._required_config['state']
|
||||
if state == 'absent':
|
||||
self._generate_no_ipl_commands()
|
||||
else:
|
||||
self._generate_ipl_commands()
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxMlagIplModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
179
lib/ansible/modules/network/onyx/onyx_mlag_vip.py
Normal file
179
lib/ansible/modules/network/onyx/onyx_mlag_vip.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_mlag_vip
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Configures MLAG VIP on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of MLAG virtual IPs
|
||||
on Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
ipaddress:
|
||||
description:
|
||||
- Virtual IP address of the MLAG. Required if I(state=present).
|
||||
group_name:
|
||||
description:
|
||||
- MLAG group name. Required if I(state=present).
|
||||
mac_address:
|
||||
description:
|
||||
- MLAG system MAC address. Required if I(state=present).
|
||||
state:
|
||||
description:
|
||||
- MLAG VIP state.
|
||||
choices: ['present', 'absent']
|
||||
delay:
|
||||
description:
|
||||
- Delay interval, in seconds, waiting for the changes on mlag VIP to take
|
||||
effect.
|
||||
default: 12
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure mlag-vip
|
||||
onyx_mlag_vip:
|
||||
ipaddress: 50.3.3.1/24
|
||||
group_name: ansible-test-group
|
||||
mac_address: 00:11:12:23:34:45
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- mlag-vip ansible_test_group ip 50.3.3.1 /24 force
|
||||
- no mlag shutdown
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxMLagVipModule(BaseOnyxModule):
|
||||
|
||||
def init_module(self):
|
||||
""" initialize module
|
||||
"""
|
||||
element_spec = dict(
|
||||
ipaddress=dict(),
|
||||
group_name=dict(),
|
||||
mac_address=dict(),
|
||||
delay=dict(type='int', default=12),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
argument_spec = dict()
|
||||
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
module_params = self._module.params
|
||||
lag_params = {
|
||||
'ipaddress': module_params['ipaddress'],
|
||||
'group_name': module_params['group_name'],
|
||||
'mac_address': module_params['mac_address'],
|
||||
'delay': module_params['delay'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
|
||||
self.validate_param_values(lag_params)
|
||||
self._required_config = lag_params
|
||||
|
||||
def _show_mlag_cmd(self, cmd):
|
||||
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
|
||||
|
||||
def _show_mlag(self):
|
||||
cmd = "show mlag"
|
||||
return self._show_mlag_cmd(cmd)
|
||||
|
||||
def _show_mlag_vip(self):
|
||||
cmd = "show mlag-vip"
|
||||
return self._show_mlag_cmd(cmd)
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
mlag_config = self._show_mlag()
|
||||
mlag_vip_config = self._show_mlag_vip()
|
||||
if mlag_vip_config:
|
||||
mlag_vip = mlag_vip_config.get("MLAG-VIP", {})
|
||||
self._current_config['group_name'] = \
|
||||
mlag_vip.get("MLAG group name")
|
||||
self._current_config['ipaddress'] = \
|
||||
mlag_vip.get("MLAG VIP address")
|
||||
if mlag_config:
|
||||
self._current_config['mac_address'] = \
|
||||
mlag_config.get("System-mac")
|
||||
|
||||
def generate_commands(self):
|
||||
state = self._required_config['state']
|
||||
if state == 'present':
|
||||
self._generate_mlag_vip_cmds()
|
||||
else:
|
||||
self._generate_no_mlag_vip_cmds()
|
||||
|
||||
def _generate_mlag_vip_cmds(self):
|
||||
current_group = self._current_config.get('group_name')
|
||||
current_ip = self._current_config.get('ipaddress')
|
||||
current_mac = self._current_config.get('mac_address')
|
||||
if current_mac:
|
||||
current_mac = current_mac.lower()
|
||||
|
||||
req_group = self._required_config.get('group_name')
|
||||
req_ip = self._required_config.get('ipaddress')
|
||||
req_mac = self._required_config.get('mac_address')
|
||||
if req_mac:
|
||||
req_mac = req_mac.lower()
|
||||
|
||||
if req_group != current_group or req_ip != current_ip:
|
||||
ipaddr, mask = req_ip.split('/')
|
||||
self._commands.append(
|
||||
'mlag-vip %s ip %s /%s force' % (req_group, ipaddr, mask))
|
||||
if req_mac != current_mac:
|
||||
self._commands.append(
|
||||
'mlag system-mac %s' % (req_mac))
|
||||
if self._commands:
|
||||
self._commands.append('no mlag shutdown')
|
||||
|
||||
def _generate_no_mlag_vip_cmds(self):
|
||||
if self._current_config.get('group_name'):
|
||||
self._commands.append('no mlag-vip')
|
||||
|
||||
def check_declarative_intent_params(self, result):
|
||||
if not result['changed']:
|
||||
return
|
||||
delay_interval = self._required_config.get('delay')
|
||||
if delay_interval > 0:
|
||||
time.sleep(delay_interval)
|
||||
for cmd in ("show mlag-vip", ""):
|
||||
show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxMLagVipModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
238
lib/ansible/modules/network/onyx/onyx_ospf.py
Normal file
238
lib/ansible/modules/network/onyx/onyx_ospf.py
Normal file
@@ -0,0 +1,238 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_ospf
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage OSPF protocol on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management and configuration of OSPF
|
||||
protocol on Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
ospf:
|
||||
description:
|
||||
- "OSPF instance number 1-65535"
|
||||
required: true
|
||||
router_id:
|
||||
description:
|
||||
- OSPF router ID. Required if I(state=present).
|
||||
interfaces:
|
||||
description:
|
||||
- List of interfaces and areas. Required if I(state=present).
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Intrface name.
|
||||
required: true
|
||||
area:
|
||||
description:
|
||||
- OSPF area.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- OSPF state.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: add ospf router to interface
|
||||
onyx_ospf:
|
||||
ospf: 2
|
||||
router_id: 192.168.8.2
|
||||
interfaces:
|
||||
- name: Eth1/1
|
||||
- area: 0.0.0.0
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- router ospf 2
|
||||
- router-id 192.168.8.2
|
||||
- exit
|
||||
- interface ethernet 1/1 ip ospf area 0.0.0.0
|
||||
"""
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxOspfModule(BaseOnyxModule):
|
||||
OSPF_IF_REGEX = re.compile(
|
||||
r'^(Loopback\d+|Eth\d+\/\d+|Vlan\d+|Po\d+)\s+(\S+).*')
|
||||
OSPF_ROUTER_REGEX = re.compile(r'^Routing Process (\d+).*ID\s+(\S+).*')
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
interface_spec = dict(
|
||||
name=dict(required=True),
|
||||
area=dict(required=True),
|
||||
)
|
||||
element_spec = dict(
|
||||
ospf=dict(type='int', required=True),
|
||||
router_id=dict(),
|
||||
interfaces=dict(type='list', elements='dict',
|
||||
options=interface_spec),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
return element_spec
|
||||
|
||||
def init_module(self):
|
||||
""" Ansible module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
argument_spec = dict()
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
def validate_ospf(self, value):
|
||||
if value and not 1 <= int(value) <= 65535:
|
||||
self._module.fail_json(msg='ospf id must be between 1 and 65535')
|
||||
|
||||
def get_required_config(self):
|
||||
module_params = self._module.params
|
||||
self._required_config = dict(
|
||||
ospf=module_params['ospf'],
|
||||
router_id=module_params['router_id'],
|
||||
state=module_params['state'],
|
||||
)
|
||||
interfaces = module_params['interfaces'] or list()
|
||||
req_interfaces = self._required_config['interfaces'] = dict()
|
||||
for interface_data in interfaces:
|
||||
req_interfaces[interface_data['name']] = interface_data['area']
|
||||
self.validate_param_values(self._required_config)
|
||||
|
||||
def _update_ospf_data(self, ospf_data):
|
||||
match = self.OSPF_ROUTER_REGEX.match(ospf_data)
|
||||
if match:
|
||||
ospf_id = int(match.group(1))
|
||||
router_id = match.group(2)
|
||||
self._current_config['ospf'] = ospf_id
|
||||
self._current_config['router_id'] = router_id
|
||||
|
||||
def _update_ospf_interfaces(self, ospf_interfaces):
|
||||
interfaces = self._current_config['interfaces'] = dict()
|
||||
lines = ospf_interfaces.split('\n')
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
match = self.OSPF_IF_REGEX.match(line)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
area = match.group(2)
|
||||
for prefix in ("Vlan", "Loopback"):
|
||||
if name.startswith(prefix):
|
||||
name = name.replace(prefix, prefix + ' ')
|
||||
interfaces[name] = area
|
||||
|
||||
def _get_ospf_config(self, ospf_id):
|
||||
cmd = 'show ip ospf %s | include Process' % ospf_id
|
||||
return show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
|
||||
|
||||
def _get_ospf_interfaces_config(self, ospf_id):
|
||||
cmd = 'show ip ospf interface %s brief' % ospf_id
|
||||
return show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
ospf_id = self._required_config['ospf']
|
||||
self._current_config = dict()
|
||||
ospf_data = self._get_ospf_config(ospf_id)
|
||||
if ospf_data:
|
||||
self._update_ospf_data(ospf_data)
|
||||
ospf_interfaces = self._get_ospf_interfaces_config(ospf_id)
|
||||
if ospf_interfaces:
|
||||
self._update_ospf_interfaces(ospf_interfaces)
|
||||
|
||||
def _generate_no_ospf_commands(self):
|
||||
req_ospf_id = self._required_config['ospf']
|
||||
curr_ospf_id = self._current_config.get('ospf')
|
||||
if curr_ospf_id == req_ospf_id:
|
||||
cmd = 'no router ospf %s' % req_ospf_id
|
||||
self._commands.append(cmd)
|
||||
|
||||
def _get_interface_command_name(self, if_name):
|
||||
if if_name.startswith('Eth'):
|
||||
return if_name.replace("Eth", "ethernet ")
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "port-channel ")
|
||||
if if_name.startswith('Vlan'):
|
||||
return if_name.replace("Vlan", "vlan")
|
||||
if if_name.startswith('Loopback'):
|
||||
return if_name.replace("Loopback", "loopback")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _get_interface_area_cmd(self, if_name, area):
|
||||
interface_prefix = self._get_interface_command_name(if_name)
|
||||
if area:
|
||||
area_cmd = 'ip ospf area %s' % area
|
||||
else:
|
||||
area_cmd = 'no ip ospf area'
|
||||
cmd = 'interface %s %s' % (interface_prefix, area_cmd)
|
||||
return cmd
|
||||
|
||||
def _generate_ospf_commands(self):
|
||||
req_router_id = self._required_config['router_id']
|
||||
req_ospf_id = self._required_config['ospf']
|
||||
curr_router_id = self._current_config.get('router_id')
|
||||
curr_ospf_id = self._current_config.get('ospf')
|
||||
if curr_ospf_id != req_ospf_id or req_router_id != curr_router_id:
|
||||
cmd = 'router ospf %s' % req_ospf_id
|
||||
self._commands.append(cmd)
|
||||
if req_router_id != curr_router_id:
|
||||
if req_router_id:
|
||||
cmd = 'router-id %s' % req_router_id
|
||||
else:
|
||||
cmd = 'no router-id'
|
||||
self._commands.append(cmd)
|
||||
self._commands.append('exit')
|
||||
req_interfaces = self._required_config['interfaces']
|
||||
curr_interfaces = self._current_config.get('interfaces', dict())
|
||||
for if_name, area in iteritems(req_interfaces):
|
||||
curr_area = curr_interfaces.get(if_name)
|
||||
if curr_area != area:
|
||||
cmd = self._get_interface_area_cmd(if_name, area)
|
||||
self._commands.append(cmd)
|
||||
for if_name in curr_interfaces:
|
||||
if if_name not in req_interfaces:
|
||||
cmd = self._get_interface_area_cmd(if_name, None)
|
||||
self._commands.append(cmd)
|
||||
|
||||
def generate_commands(self):
|
||||
req_state = self._required_config['state']
|
||||
if req_state == 'absent':
|
||||
return self._generate_no_ospf_commands()
|
||||
return self._generate_ospf_commands()
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxOspfModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
205
lib/ansible/modules/network/onyx/onyx_pfc_interface.py
Normal file
205
lib/ansible/modules/network/onyx/onyx_pfc_interface.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_pfc_interface
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Manage priority flow control on ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of priority flow control (PFC)
|
||||
on interfaces of Mellanox ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the interface PFC should be configured on.
|
||||
aggregate:
|
||||
description: List of interfaces PFC should be configured on.
|
||||
purge:
|
||||
description:
|
||||
- Purge interfaces not defined in the aggregate parameter.
|
||||
type: bool
|
||||
default: false
|
||||
state:
|
||||
description:
|
||||
- State of the PFC configuration.
|
||||
default: enabled
|
||||
choices: ['enabled', 'disabled']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure PFC
|
||||
onyx_pfc_interface:
|
||||
name: Eth1/1
|
||||
state: enabled
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- interface ethernet 1/17 dcb priority-flow-control mode on
|
||||
"""
|
||||
from copy import deepcopy
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxPfcInterfaceModule(BaseOnyxModule):
|
||||
PFC_IF_REGEX = re.compile(
|
||||
r"^(Eth\d+\/\d+)|(Eth\d+\/\d+\/\d+)|(Po\d+)|(Mpo\d+)$")
|
||||
|
||||
_purge = False
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
name=dict(type='str'),
|
||||
state=dict(default='enabled',
|
||||
choices=['enabled', 'disabled']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
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)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['name', 'aggregate']]
|
||||
mutually_exclusive = [['name', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
aggregate = module_params.get('aggregate')
|
||||
self._purge = module_params.get('purge', False)
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module_params[key]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'name': module_params['name'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
def _create_if_pfc_data(self, if_name, if_pfc_data):
|
||||
state = self.get_config_attr(if_pfc_data, "PFC oper")
|
||||
state = state.lower()
|
||||
return dict(
|
||||
name=if_name,
|
||||
state=state)
|
||||
|
||||
def _get_pfc_config(self):
|
||||
return show_cmd(self._module, "show dcb priority-flow-control")
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
pfc_config = self._get_pfc_config()
|
||||
if not pfc_config:
|
||||
return
|
||||
if 'Table 2' in pfc_config:
|
||||
pfc_config = pfc_config['Table 2']
|
||||
for if_name, if_pfc_data in iteritems(pfc_config):
|
||||
match = self.PFC_IF_REGEX.match(if_name)
|
||||
if not match:
|
||||
continue
|
||||
if if_pfc_data:
|
||||
if_pfc_data = if_pfc_data[0]
|
||||
self._current_config[if_name] = \
|
||||
self._create_if_pfc_data(if_name, if_pfc_data)
|
||||
|
||||
def _get_interface_cmd_name(self, if_name):
|
||||
if if_name.startswith('Eth'):
|
||||
return if_name.replace("Eth", "ethernet ")
|
||||
if if_name.startswith('Po'):
|
||||
return if_name.replace("Po", "port-channel ")
|
||||
if if_name.startswith('Mpo'):
|
||||
return if_name.replace("Mpo", "mlag-port-channel ")
|
||||
self._module.fail_json(
|
||||
msg='invalid interface name: %s' % if_name)
|
||||
|
||||
def _add_if_pfc_commands(self, if_name, req_state):
|
||||
cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
|
||||
|
||||
if req_state == 'disabled':
|
||||
pfc_cmd = 'no dcb priority-flow-control mode force'
|
||||
else:
|
||||
pfc_cmd = 'dcb priority-flow-control mode on force'
|
||||
self._commands.append(cmd_prefix + pfc_cmd)
|
||||
|
||||
def _gen_pfc_commands(self, if_name, curr_conf, req_state):
|
||||
curr_state = curr_conf.get('state', 'disabled')
|
||||
if curr_state != req_state:
|
||||
self._add_if_pfc_commands(if_name, req_state)
|
||||
|
||||
def generate_commands(self):
|
||||
req_interfaces = set()
|
||||
for req_conf in self._required_config:
|
||||
req_state = req_conf['state']
|
||||
if_name = req_conf['name']
|
||||
if req_state == 'enabled':
|
||||
req_interfaces.add(if_name)
|
||||
curr_conf = self._current_config.get(if_name, {})
|
||||
self._gen_pfc_commands(if_name, curr_conf, req_state)
|
||||
if self._purge:
|
||||
for if_name, curr_conf in iteritems(self._current_config):
|
||||
if if_name not in req_interfaces:
|
||||
req_state = 'disabled'
|
||||
self._gen_pfc_commands(if_name, curr_conf, req_state)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxPfcInterfaceModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
185
lib/ansible/modules/network/onyx/onyx_protocol.py
Normal file
185
lib/ansible/modules/network/onyx/onyx_protocol.py
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_protocol
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd)"
|
||||
short_description: Enables/Disables protocols on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides a mechanism for enabling and disabling protocols
|
||||
Mellanox on ONYX network devices.
|
||||
notes:
|
||||
- Tested on ONYX 3.6.4000
|
||||
options:
|
||||
mlag:
|
||||
description: MLAG protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
magp:
|
||||
description: MAGP protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
spanning_tree:
|
||||
description: Spanning Tree support
|
||||
choices: ['enabled', 'disabled']
|
||||
dcb_pfc:
|
||||
description: DCB priority flow control
|
||||
choices: ['enabled', 'disabled']
|
||||
igmp_snooping:
|
||||
description: IP IGMP snooping
|
||||
choices: ['enabled', 'disabled']
|
||||
lacp:
|
||||
description: LACP protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
ip_l3:
|
||||
description: IP L3 support
|
||||
choices: ['enabled', 'disabled']
|
||||
ip_routing:
|
||||
description: IP routing support
|
||||
choices: ['enabled', 'disabled']
|
||||
lldp:
|
||||
description: LLDP protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
bgp:
|
||||
description: BGP protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
ospf:
|
||||
description: OSPF protocol
|
||||
choices: ['enabled', 'disabled']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: enable protocols for MLAG
|
||||
onyx_protocol:
|
||||
lacp: enabled
|
||||
spanning_tree: disabled
|
||||
ip_routing: enabled
|
||||
mlag: enabled
|
||||
dcb_pfc: enabled
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- no spanning-tree
|
||||
- protocol mlag
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxProtocolModule(BaseOnyxModule):
|
||||
|
||||
PROTOCOL_MAPPING = dict(
|
||||
mlag=dict(name="mlag", enable="protocol mlag",
|
||||
disable="no protocol mlag"),
|
||||
magp=dict(name="magp", enable="protocol magp",
|
||||
disable="no protocol magp"),
|
||||
spanning_tree=dict(name="spanning-tree", enable="spanning-tree",
|
||||
disable="no spanning-tree"),
|
||||
dcb_pfc=dict(name="priority-flow-control",
|
||||
enable="dcb priority-flow-control enable force",
|
||||
disable="no dcb priority-flow-control enable force"),
|
||||
igmp_snooping=dict(name="igmp-snooping", enable="ip igmp snooping",
|
||||
disable="no ip igmp snooping"),
|
||||
lacp=dict(name="lacp", enable="lacp", disable="no lacp"),
|
||||
ip_l3=dict(name="IP L3", enable="ip l3",
|
||||
disable="no ip l3"),
|
||||
ip_routing=dict(name="IP routing", enable="ip routing",
|
||||
disable="no ip routing"),
|
||||
lldp=dict(name="lldp", enable="lldp", disable="no lldp"),
|
||||
bgp=dict(name="bgp", enable="protocol bgp", disable="no protocol bgp"),
|
||||
ospf=dict(name="ospf", enable="protocol ospf",
|
||||
disable="no protocol ospf"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
element_spec = dict()
|
||||
for protocol in cls.PROTOCOL_MAPPING:
|
||||
element_spec[protocol] = dict(choices=['enabled', 'disabled'])
|
||||
return element_spec
|
||||
|
||||
def init_module(self):
|
||||
""" Ansible module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
argument_spec = dict()
|
||||
argument_spec.update(element_spec)
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = dict()
|
||||
module_params = self._module.params
|
||||
for key, val in iteritems(module_params):
|
||||
if key in self.PROTOCOL_MAPPING and val is not None:
|
||||
self._required_config[key] = val
|
||||
|
||||
def _get_protocols(self):
|
||||
return show_cmd(self._module, "show protocols")
|
||||
|
||||
def _get_ip_routing(self):
|
||||
return show_cmd(self._module, 'show ip routing | include "IP routing"',
|
||||
json_fmt=False)
|
||||
|
||||
def load_current_config(self):
|
||||
self._current_config = dict()
|
||||
protocols_config = self._get_protocols()
|
||||
if not protocols_config:
|
||||
protocols_config = dict()
|
||||
ip_config = self._get_ip_routing()
|
||||
if ip_config:
|
||||
lines = ip_config.split('\n')
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
line_attr = line.split(':')
|
||||
if len(line_attr) == 2:
|
||||
attr = line_attr[0].strip()
|
||||
val = line_attr[1].strip()
|
||||
protocols_config[attr] = val
|
||||
for protocol, protocol_metadata in iteritems(self.PROTOCOL_MAPPING):
|
||||
protocol_json_attr = protocol_metadata['name']
|
||||
val = protocols_config.get(protocol_json_attr, 'disabled')
|
||||
if val not in ('enabled', 'disabled'):
|
||||
val = 'enabled'
|
||||
self._current_config[protocol] = val
|
||||
|
||||
def generate_commands(self):
|
||||
for protocol, req_val in iteritems(self._required_config):
|
||||
protocol_metadata = self.PROTOCOL_MAPPING[protocol]
|
||||
curr_val = self._current_config.get(protocol, 'disabled')
|
||||
if curr_val != req_val:
|
||||
if req_val == 'disabled':
|
||||
command = protocol_metadata['disable']
|
||||
else:
|
||||
command = protocol_metadata['enable']
|
||||
self._commands.append(command)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxProtocolModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
201
lib/ansible/modules/network/onyx/onyx_vlan.py
Normal file
201
lib/ansible/modules/network/onyx/onyx_vlan.py
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/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
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: onyx_vlan
|
||||
version_added: "2.5"
|
||||
author: "Samer Deeb (@samerd) Alex Tabachnik (@atabachnik)"
|
||||
short_description: Manage VLANs on Mellanox ONYX network devices
|
||||
description:
|
||||
- This module provides declarative management of VLANs
|
||||
on Mellanox ONYX network devices.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the VLAN.
|
||||
vlan_id:
|
||||
description:
|
||||
- ID of the VLAN.
|
||||
aggregate:
|
||||
description: List of VLANs definitions.
|
||||
purge:
|
||||
description:
|
||||
- Purge VLANs not defined in the I(aggregate) parameter.
|
||||
default: no
|
||||
state:
|
||||
description:
|
||||
- State of the VLAN configuration.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure VLAN ID and name
|
||||
onyx_vlan:
|
||||
vlan_id: 20
|
||||
name: test-vlan
|
||||
|
||||
- name: remove configuration
|
||||
onyx_vlan:
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The list of configuration mode commands to send to the device
|
||||
returned: always.
|
||||
type: list
|
||||
sample:
|
||||
- vlan 20
|
||||
- name test-vlan
|
||||
- exit
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||
|
||||
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
|
||||
from ansible.module_utils.network.onyx.onyx import show_cmd
|
||||
|
||||
|
||||
class OnyxVlanModule(BaseOnyxModule):
|
||||
_purge = False
|
||||
|
||||
@classmethod
|
||||
def _get_element_spec(cls):
|
||||
return dict(
|
||||
vlan_id=dict(type='int'),
|
||||
name=dict(type='str'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_aggregate_spec(cls, element_spec):
|
||||
aggregate_spec = deepcopy(element_spec)
|
||||
aggregate_spec['vlan_id'] = dict(required=True)
|
||||
|
||||
# remove default in aggregate spec, to handle common arguments
|
||||
remove_default_spec(aggregate_spec)
|
||||
return aggregate_spec
|
||||
|
||||
def init_module(self):
|
||||
""" module initialization
|
||||
"""
|
||||
element_spec = self._get_element_spec()
|
||||
aggregate_spec = self._get_aggregate_spec(element_spec)
|
||||
argument_spec = dict(
|
||||
aggregate=dict(type='list', elements='dict',
|
||||
options=aggregate_spec),
|
||||
purge=dict(default=False, type='bool'),
|
||||
)
|
||||
argument_spec.update(element_spec)
|
||||
required_one_of = [['vlan_id', 'aggregate']]
|
||||
mutually_exclusive = [['vlan_id', 'aggregate']]
|
||||
self._module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_one_of=required_one_of,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True)
|
||||
|
||||
def validate_vlan_id(self, value):
|
||||
if value and not 1 <= int(value) <= 4094:
|
||||
self._module.fail_json(msg='vlan id must be between 1 and 4094')
|
||||
|
||||
def get_required_config(self):
|
||||
self._required_config = list()
|
||||
module_params = self._module.params
|
||||
aggregate = module_params.get('aggregate')
|
||||
self._purge = module_params.get('purge', False)
|
||||
if aggregate:
|
||||
for item in aggregate:
|
||||
for key in item:
|
||||
if item.get(key) is None:
|
||||
item[key] = module_params[key]
|
||||
self.validate_param_values(item, item)
|
||||
req_item = item.copy()
|
||||
req_item['vlan_id'] = int(req_item['vlan_id'])
|
||||
self._required_config.append(req_item)
|
||||
else:
|
||||
params = {
|
||||
'vlan_id': module_params['vlan_id'],
|
||||
'name': module_params['name'],
|
||||
'state': module_params['state'],
|
||||
}
|
||||
self.validate_param_values(params)
|
||||
self._required_config.append(params)
|
||||
|
||||
def _create_vlan_data(self, vlan_id, vlan_data):
|
||||
return {
|
||||
'vlan_id': vlan_id,
|
||||
'name': self.get_config_attr(vlan_data, 'Name')
|
||||
}
|
||||
|
||||
def _get_vlan_config(self):
|
||||
return show_cmd(self._module, "show vlan")
|
||||
|
||||
def load_current_config(self):
|
||||
# called in base class in run function
|
||||
self._current_config = dict()
|
||||
vlan_config = self._get_vlan_config()
|
||||
if not vlan_config:
|
||||
return
|
||||
for vlan_id, vlan_data in iteritems(vlan_config):
|
||||
try:
|
||||
vlan_id = int(vlan_id)
|
||||
except ValueError:
|
||||
continue
|
||||
self._current_config[vlan_id] = \
|
||||
self._create_vlan_data(vlan_id, vlan_data)
|
||||
|
||||
def generate_commands(self):
|
||||
req_vlans = set()
|
||||
for req_conf in self._required_config:
|
||||
state = req_conf['state']
|
||||
vlan_id = req_conf['vlan_id']
|
||||
if state == 'absent':
|
||||
if vlan_id in self._current_config:
|
||||
self._commands.append('no vlan %s' % vlan_id)
|
||||
else:
|
||||
req_vlans.add(vlan_id)
|
||||
self._generate_vlan_commands(vlan_id, req_conf)
|
||||
if self._purge:
|
||||
for vlan_id in self._current_config:
|
||||
if vlan_id not in req_vlans:
|
||||
self._commands.append('no vlan %s' % vlan_id)
|
||||
|
||||
def _generate_vlan_commands(self, vlan_id, req_conf):
|
||||
curr_vlan = self._current_config.get(vlan_id, {})
|
||||
if not curr_vlan:
|
||||
self._commands.append("vlan %s" % vlan_id)
|
||||
self._commands.append("exit")
|
||||
req_name = req_conf['name']
|
||||
curr_name = curr_vlan.get('name')
|
||||
if req_name:
|
||||
if req_name != curr_name:
|
||||
self._commands.append("vlan %s name %s" % (vlan_id, req_name))
|
||||
elif req_name is not None:
|
||||
if curr_name:
|
||||
self._commands.append("vlan %s no name" % vlan_id)
|
||||
|
||||
|
||||
def main():
|
||||
""" main entry point for module execution
|
||||
"""
|
||||
OnyxVlanModule.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user