mirror of https://github.com/ansible/ansible.git
NX-OS Telemetry Resource Module (#59126)
* Combined telemetry module commit * Minor fixes * Add back whitespace * Add telemetry subscription support and simplify * Remove comment line * Make ansibot happy * Create common build_args method * More ansibot fixes * Refactored integration tests, remove old files * Add subscription tests * Add integration tests * Update module docs * Test updates * Address review comments * Comment should be one line, not two * Address Trishna comments * State deleted should purge all config * Remove misleading comment * Doc fixes * Fix source int bug and remove local debug msg * Add additional integration test checkspull/60055/head
parent
178db5f3ed
commit
9610f2b8ac
@ -0,0 +1,90 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#############################################
|
||||
# WARNING #
|
||||
#############################################
|
||||
#
|
||||
# This file is auto generated by the resource
|
||||
# module builder playbook.
|
||||
#
|
||||
# Do not edit this file manually.
|
||||
#
|
||||
# Changes to this file will be over written
|
||||
# by the resource module builder.
|
||||
#
|
||||
# Changes should be made in the model used to
|
||||
# generate this file or in the resource module
|
||||
# builder template.
|
||||
#
|
||||
#############################################
|
||||
|
||||
"""
|
||||
The arg spec for the nxos_telemetry module
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class TelemetryArgs(object): # pylint: disable=R0903
|
||||
"""The arg spec for the nxos_telemetry module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'config': {
|
||||
'options': {
|
||||
'certificate': {
|
||||
'options': {
|
||||
'hostname': {'type': 'str'},
|
||||
'key': {'type': 'str'}},
|
||||
'type': 'dict'},
|
||||
'compression': {'choices': ['gzip'], 'type': 'str'},
|
||||
'source_interface': {'type': 'str'},
|
||||
'vrf': {'type': 'str'},
|
||||
'destination_groups': {
|
||||
'options': {
|
||||
'destination': {
|
||||
'options': {
|
||||
'encoding': {'choices': ['GPB', 'JSON'],
|
||||
'type': 'str'},
|
||||
'ip': {'type': 'str'},
|
||||
'port': {'type': 'int'},
|
||||
'protocol': {'choices': ['HTTP', 'TCP', 'UDP', 'gRPC'],
|
||||
'type': 'str'}},
|
||||
'type': 'dict'},
|
||||
'id': {'type': 'int'}},
|
||||
'type': 'list'},
|
||||
'sensor_groups': {
|
||||
'options': {
|
||||
'data_source': {'choices': ['NX-API', 'DME', 'YANG'],
|
||||
'type': 'str'},
|
||||
'id': {'type': 'int'},
|
||||
'path': {
|
||||
'options': {
|
||||
'depth': {'type': 'str'},
|
||||
'filter_condition': {'type': 'str'},
|
||||
'name': {'type': 'str'},
|
||||
'query_condition': {'type': 'str'}},
|
||||
'type': 'dict'}},
|
||||
'type': 'list'},
|
||||
'subscriptions': {
|
||||
'options': {
|
||||
'destination_group': {'type': 'int'},
|
||||
'id': {'type': 'int'},
|
||||
'sensor_group': {
|
||||
'options': {
|
||||
'id': {'type': 'int'},
|
||||
'sample_interval': {'type': 'int'}},
|
||||
'type': 'dict'}},
|
||||
'type': 'list'}},
|
||||
'type': 'dict'},
|
||||
'state': {
|
||||
'choices': ['merged', 'replaced', 'deleted'],
|
||||
'default': 'merged',
|
||||
'type': 'str'}} # pylint: disable=C0301
|
@ -0,0 +1,145 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
#
|
||||
# Telemetry Command Reference File
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
TMS_GLOBAL = '''
|
||||
# The cmd_ref is a yaml formatted list of module commands.
|
||||
# A leading underscore denotes a non-command variable; e.g. _template.
|
||||
# TMS does not have convenient global json data so this cmd_ref uses raw cli configs.
|
||||
---
|
||||
_template: # _template holds common settings for all commands
|
||||
# Enable feature telemetry if disabled
|
||||
feature: telemetry
|
||||
# Common get syntax for TMS commands
|
||||
get_command: show run telemetry all
|
||||
# Parent configuration for TMS commands
|
||||
context:
|
||||
- telemetry
|
||||
certificate:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
kind: dict
|
||||
getval: certificate (?P<key>\\S+) (?P<hostname>\\S+)$
|
||||
setval: certificate {key} {hostname}
|
||||
default:
|
||||
key: ~
|
||||
hostname: ~
|
||||
compression:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
kind: str
|
||||
getval: use-compression (\\S+)$
|
||||
setval: 'use-compression {0}'
|
||||
default: ~
|
||||
context: &dpcontext
|
||||
- telemetry
|
||||
- destination-profile
|
||||
source_interface:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
kind: str
|
||||
getval: source-interface (\\S+)$
|
||||
setval: 'source-interface {0}'
|
||||
default: ~
|
||||
context: *dpcontext
|
||||
vrf:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
kind: str
|
||||
getval: use-vrf (\\S+)$
|
||||
setval: 'use-vrf {0}'
|
||||
default: ~
|
||||
context: *dpcontext
|
||||
'''
|
||||
|
||||
TMS_DESTGROUP = '''
|
||||
# The cmd_ref is a yaml formatted list of module commands.
|
||||
# A leading underscore denotes a non-command variable; e.g. _template.
|
||||
# TBD: Use Structured Where Possible
|
||||
---
|
||||
_template: # _template holds common settings for all commands
|
||||
# Enable feature telemetry if disabled
|
||||
feature: telemetry
|
||||
# Common get syntax for TMS commands
|
||||
get_command: show run telemetry all
|
||||
# Parent configuration for TMS commands
|
||||
context:
|
||||
- telemetry
|
||||
destination:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
multiple: true
|
||||
kind: dict
|
||||
getval: ip address (?P<ip>\\S+) port (?P<port>\\S+) protocol (?P<protocol>\\S+) encoding (?P<encoding>\\S+)
|
||||
setval: ip address {ip} port {port} protocol {protocol} encoding {encoding}
|
||||
default:
|
||||
ip: ~
|
||||
port: ~
|
||||
protocol: ~
|
||||
encoding: ~
|
||||
'''
|
||||
|
||||
TMS_SENSORGROUP = '''
|
||||
# The cmd_ref is a yaml formatted list of module commands.
|
||||
# A leading underscore denotes a non-command variable; e.g. _template.
|
||||
# TBD: Use Structured Where Possible
|
||||
---
|
||||
_template: # _template holds common settings for all commands
|
||||
# Enable feature telemetry if disabled
|
||||
feature: telemetry
|
||||
# Common get syntax for TMS commands
|
||||
get_command: show run telemetry all
|
||||
# Parent configuration for TMS commands
|
||||
context:
|
||||
- telemetry
|
||||
data_source:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
kind: str
|
||||
getval: data-source (\\S+)$
|
||||
setval: 'data-source {0}'
|
||||
default: ~
|
||||
path:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
multiple: true
|
||||
kind: dict
|
||||
getval: path (?P<name>\\S+)( depth (?P<depth>\\S+))?( query-condition (?P<query_condition>\\S+))?( filter-condition (?P<filter_condition>\\S+))?$
|
||||
setval: path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
|
||||
default:
|
||||
name: ~
|
||||
depth: ~
|
||||
query_condition: ~
|
||||
filter_condition: ~
|
||||
'''
|
||||
|
||||
TMS_SUBSCRIPTION = '''
|
||||
# The cmd_ref is a yaml formatted list of module commands.
|
||||
# A leading underscore denotes a non-command variable; e.g. _template.
|
||||
# TBD: Use Structured Where Possible
|
||||
---
|
||||
_template: # _template holds common settings for all commands
|
||||
# Enable feature telemetry if disabled
|
||||
feature: telemetry
|
||||
# Common get syntax for TMS commands
|
||||
get_command: show run telemetry all
|
||||
# Parent configuration for TMS commands
|
||||
context:
|
||||
- telemetry
|
||||
destination_group:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
multiple: true
|
||||
kind: int
|
||||
getval: dst-grp (\\S+)$
|
||||
setval: 'dst-grp {0}'
|
||||
default: ~
|
||||
sensor_group:
|
||||
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
|
||||
multiple: true
|
||||
kind: dict
|
||||
getval: snsr-grp (?P<id>\\S+) sample-interval (?P<sample_interval>\\S+)$
|
||||
setval: snsr-grp {id} sample-interval {sample_interval}
|
||||
default:
|
||||
id: ~
|
||||
sample_interval: ~
|
||||
'''
|
@ -0,0 +1,286 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The nxos_telemetry class
|
||||
It is in this file where the current configuration (as dict)
|
||||
is compared to the provided configuration (as dict) and the command set
|
||||
necessary to bring the current configuration to it's desired end-state is
|
||||
created
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.module_utils.network.nxos.facts.facts import Facts
|
||||
from ansible.module_utils.network.nxos.cmdref.telemetry.telemetry import TMS_GLOBAL, TMS_DESTGROUP, TMS_SENSORGROUP, TMS_SUBSCRIPTION
|
||||
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import normalize_data, remove_duplicate_context
|
||||
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import valiate_input, get_setval_path
|
||||
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import get_module_params_subsection
|
||||
from ansible.module_utils.network.nxos.utils.utils import normalize_interface
|
||||
from ansible.module_utils.network.nxos.nxos import NxosCmdRef
|
||||
|
||||
|
||||
class Telemetry(ConfigBase):
|
||||
"""
|
||||
The nxos_telemetry class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'telemetry',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Telemetry, self).__init__(module)
|
||||
|
||||
def get_telemetry_facts(self):
|
||||
""" Get the 'facts' (the current configuration)
|
||||
|
||||
:rtype: A dictionary
|
||||
:returns: The current configuration as a dictionary
|
||||
"""
|
||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
||||
telemetry_facts = facts['ansible_network_resources'].get('telemetry')
|
||||
if not telemetry_facts:
|
||||
return {}
|
||||
return telemetry_facts
|
||||
|
||||
def edit_config(self, commands):
|
||||
return self._connection.edit_config(commands)
|
||||
|
||||
def execute_module(self):
|
||||
""" Execute the module
|
||||
:rtype: A dictionary
|
||||
:returns: The result from module execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
commands = list()
|
||||
warnings = list()
|
||||
|
||||
state = self._module.params['state']
|
||||
if 'overridden' in state:
|
||||
self._module.fail_json(msg='State <overridden> is invalid for this module.')
|
||||
if 'replaced' in state:
|
||||
self._module.fail_json(msg='State: <replaced> not yet supported')
|
||||
|
||||
# When state is 'deleted', the module_params should not contain data
|
||||
# under the 'config' key
|
||||
if 'deleted' in state and self._module.params.get('config'):
|
||||
self._module.fail_json(msg='Remove config key from playbook when state is <deleted>')
|
||||
|
||||
if self._module.params['config'] is None:
|
||||
self._module.params['config'] = {}
|
||||
# Normalize interface name.
|
||||
int = self._module.params['config'].get('source_interface')
|
||||
if int:
|
||||
self._module.params['config']['source_interface'] = normalize_interface(int)
|
||||
|
||||
existing_telemetry_facts = self.get_telemetry_facts()
|
||||
commands.extend(self.set_config(existing_telemetry_facts))
|
||||
if commands:
|
||||
if not self._module.check_mode:
|
||||
self.edit_config(commands)
|
||||
# TODO: edit_config is only available for network_cli. Once we
|
||||
# add support for httpapi, we will need to switch to load_config
|
||||
# or add support to httpapi for edit_config
|
||||
#
|
||||
# self._connection.load_config(commands)
|
||||
result['changed'] = True
|
||||
result['commands'] = commands
|
||||
|
||||
changed_telemetry_facts = self.get_telemetry_facts()
|
||||
|
||||
result['before'] = existing_telemetry_facts
|
||||
if result['changed']:
|
||||
result['after'] = changed_telemetry_facts
|
||||
|
||||
result['warnings'] = warnings
|
||||
return result
|
||||
|
||||
def set_config(self, existing_tms_global_facts):
|
||||
""" Collect the configuration from the args passed to the module,
|
||||
collect the current configuration (as a dict from facts)
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
config = self._module.params['config']
|
||||
want = dict((k, v) for k, v in config.items() if v is not None)
|
||||
have = existing_tms_global_facts
|
||||
resp = self.set_state(want, have)
|
||||
return to_list(resp)
|
||||
|
||||
def set_state(self, want, have):
|
||||
""" Select the appropriate function based on the state provided
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
state = self._module.params['state']
|
||||
|
||||
# The deleted case is very simple since we purge all telemetry config
|
||||
# and does not require any processing using NxosCmdRef objects.
|
||||
if state == 'deleted':
|
||||
return self._state_deleted(want, have)
|
||||
|
||||
# Save off module params
|
||||
ALL_MP = self._module.params['config']
|
||||
|
||||
cmd_ref = {}
|
||||
cmd_ref['TMS_GLOBAL'] = {}
|
||||
cmd_ref['TMS_DESTGROUP'] = {}
|
||||
cmd_ref['TMS_SENSORGROUP'] = {}
|
||||
cmd_ref['TMS_SUBSCRIPTION'] = {}
|
||||
|
||||
# Get Telemetry Global Data
|
||||
cmd_ref['TMS_GLOBAL']['ref'] = []
|
||||
self._module.params['config'] = get_module_params_subsection(ALL_MP, 'TMS_GLOBAL')
|
||||
cmd_ref['TMS_GLOBAL']['ref'].append(NxosCmdRef(self._module, TMS_GLOBAL))
|
||||
ref = cmd_ref['TMS_GLOBAL']['ref'][0]
|
||||
ref.set_context()
|
||||
ref.get_existing()
|
||||
ref.get_playvals()
|
||||
device_cache = ref.cache_existing
|
||||
|
||||
if device_cache is None:
|
||||
device_cache_lines = []
|
||||
else:
|
||||
device_cache_lines = device_cache.split("\n")
|
||||
|
||||
# Get Telemetry Destination Group Data
|
||||
if want.get('destination_groups'):
|
||||
td = {'name': 'destination_groups', 'type': 'TMS_DESTGROUP',
|
||||
'obj': TMS_DESTGROUP, 'cmd': 'destination-group {0}'}
|
||||
cmd_ref[td['type']]['ref'] = []
|
||||
saved_ids = []
|
||||
for playvals in want[td['name']]:
|
||||
valiate_input(playvals, td['name'], self._module)
|
||||
if playvals['id'] in saved_ids:
|
||||
continue
|
||||
saved_ids.append(playvals['id'])
|
||||
resource_key = td['cmd'].format(playvals['id'])
|
||||
# Only build the NxosCmdRef object for the destination group module parameters.
|
||||
self._module.params['config'] = get_module_params_subsection(ALL_MP, td['type'], playvals['id'])
|
||||
cmd_ref[td['type']]['ref'].append(NxosCmdRef(self._module, td['obj']))
|
||||
ref = cmd_ref[td['type']]['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
ref.get_existing(device_cache)
|
||||
ref.get_playvals()
|
||||
normalize_data(ref)
|
||||
|
||||
# Get Telemetry Sensor Group Data
|
||||
if want.get('sensor_groups'):
|
||||
td = {'name': 'sensor_groups', 'type': 'TMS_SENSORGROUP',
|
||||
'obj': TMS_SENSORGROUP, 'cmd': 'sensor-group {0}'}
|
||||
cmd_ref[td['type']]['ref'] = []
|
||||
saved_ids = []
|
||||
for playvals in want[td['name']]:
|
||||
valiate_input(playvals, td['name'], self._module)
|
||||
if playvals['id'] in saved_ids:
|
||||
continue
|
||||
saved_ids.append(playvals['id'])
|
||||
resource_key = td['cmd'].format(playvals['id'])
|
||||
# Only build the NxosCmdRef object for the sensor group module parameters.
|
||||
self._module.params['config'] = get_module_params_subsection(ALL_MP, td['type'], playvals['id'])
|
||||
cmd_ref[td['type']]['ref'].append(NxosCmdRef(self._module, td['obj']))
|
||||
ref = cmd_ref[td['type']]['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
if get_setval_path(self._module):
|
||||
# Sensor group path setting can contain optional values.
|
||||
# Call get_setval_path helper function to process any
|
||||
# optional setval keys.
|
||||
ref._ref['path']['setval'] = get_setval_path(self._module)
|
||||
ref.get_existing(device_cache)
|
||||
ref.get_playvals()
|
||||
|
||||
# Get Telemetry Subscription Data
|
||||
if want.get('subscriptions'):
|
||||
td = {'name': 'subscriptions', 'type': 'TMS_SUBSCRIPTION',
|
||||
'obj': TMS_SUBSCRIPTION, 'cmd': 'subscription {0}'}
|
||||
cmd_ref[td['type']]['ref'] = []
|
||||
saved_ids = []
|
||||
for playvals in want[td['name']]:
|
||||
valiate_input(playvals, td['name'], self._module)
|
||||
if playvals['id'] in saved_ids:
|
||||
continue
|
||||
saved_ids.append(playvals['id'])
|
||||
resource_key = td['cmd'].format(playvals['id'])
|
||||
# Only build the NxosCmdRef object for the subscription module parameters.
|
||||
self._module.params['config'] = get_module_params_subsection(ALL_MP, td['type'], playvals['id'])
|
||||
cmd_ref[td['type']]['ref'].append(NxosCmdRef(self._module, td['obj']))
|
||||
ref = cmd_ref[td['type']]['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
ref.get_existing(device_cache)
|
||||
ref.get_playvals()
|
||||
|
||||
if state == 'overridden':
|
||||
if want == have:
|
||||
return []
|
||||
commands = self._state_overridden(cmd_ref, want, have)
|
||||
elif state == 'merged':
|
||||
if want == have:
|
||||
return []
|
||||
commands = self._state_merged(cmd_ref)
|
||||
elif state == 'replaced':
|
||||
if want == have:
|
||||
return []
|
||||
commands = self._state_replaced(cmd_ref)
|
||||
return commands
|
||||
|
||||
@staticmethod
|
||||
def _state_replaced(cmd_ref):
|
||||
""" The command generator when state is replaced
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
return commands
|
||||
|
||||
@staticmethod
|
||||
def _state_merged(cmd_ref):
|
||||
""" The command generator when state is merged
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
commands = cmd_ref['TMS_GLOBAL']['ref'][0].get_proposed()
|
||||
|
||||
if cmd_ref['TMS_DESTGROUP'].get('ref'):
|
||||
for cr in cmd_ref['TMS_DESTGROUP']['ref']:
|
||||
commands.extend(cr.get_proposed())
|
||||
|
||||
if cmd_ref['TMS_SENSORGROUP'].get('ref'):
|
||||
for cr in cmd_ref['TMS_SENSORGROUP']['ref']:
|
||||
commands.extend(cr.get_proposed())
|
||||
|
||||
if cmd_ref['TMS_SUBSCRIPTION'].get('ref'):
|
||||
for cr in cmd_ref['TMS_SUBSCRIPTION']['ref']:
|
||||
commands.extend(cr.get_proposed())
|
||||
|
||||
return remove_duplicate_context(commands)
|
||||
|
||||
@staticmethod
|
||||
def _state_deleted(want, have):
|
||||
""" The command generator when state is deleted
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
if want != have:
|
||||
commands = ['no telemetry']
|
||||
return commands
|
@ -0,0 +1,163 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The nxos telemetry fact class
|
||||
It is in this file the configuration is collected from the device
|
||||
for a given resource, parsed, and the facts tree is populated
|
||||
based on the configuration.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.nxos.argspec.telemetry.telemetry import TelemetryArgs
|
||||
from ansible.module_utils.network.nxos.cmdref.telemetry.telemetry import TMS_GLOBAL, TMS_DESTGROUP, TMS_SENSORGROUP, TMS_SUBSCRIPTION
|
||||
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import get_instance_data, cr_key_lookup
|
||||
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import normalize_data
|
||||
from ansible.module_utils.network.nxos.nxos import NxosCmdRef, normalize_interface
|
||||
|
||||
|
||||
class TelemetryFacts(object):
|
||||
""" The nxos telemetry fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
self._module = module
|
||||
self.argument_spec = TelemetryArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for telemetry
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
if connection: # just for linting purposes, remove
|
||||
pass
|
||||
|
||||
cmd_ref = {}
|
||||
cmd_ref['TMS_GLOBAL'] = {}
|
||||
cmd_ref['TMS_DESTGROUP'] = {}
|
||||
cmd_ref['TMS_SENSORGROUP'] = {}
|
||||
cmd_ref['TMS_SUBSCRIPTION'] = {}
|
||||
|
||||
# For fact gathering, module state should be 'present' when using
|
||||
# NxosCmdRef to query state
|
||||
if self._module.params.get('state'):
|
||||
saved_module_state = self._module.params['state']
|
||||
self._module.params['state'] = 'present'
|
||||
|
||||
# Get Telemetry Global Data
|
||||
cmd_ref['TMS_GLOBAL']['ref'] = []
|
||||
cmd_ref['TMS_GLOBAL']['ref'].append(NxosCmdRef(self._module, TMS_GLOBAL))
|
||||
ref = cmd_ref['TMS_GLOBAL']['ref'][0]
|
||||
ref.set_context()
|
||||
ref.get_existing()
|
||||
device_cache = ref.cache_existing
|
||||
|
||||
if device_cache is None:
|
||||
device_cache_lines = []
|
||||
else:
|
||||
device_cache_lines = device_cache.split("\n")
|
||||
|
||||
# Get Telemetry Destination Group Data
|
||||
cmd_ref['TMS_DESTGROUP']['ref'] = []
|
||||
for line in device_cache_lines:
|
||||
if re.search(r'destination-group', line):
|
||||
resource_key = line.strip()
|
||||
cmd_ref['TMS_DESTGROUP']['ref'].append(NxosCmdRef(self._module, TMS_DESTGROUP))
|
||||
ref = cmd_ref['TMS_DESTGROUP']['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
ref.get_existing(device_cache)
|
||||
normalize_data(ref)
|
||||
|
||||
# Get Telemetry Sensorgroup Group Data
|
||||
cmd_ref['TMS_SENSORGROUP']['ref'] = []
|
||||
for line in device_cache_lines:
|
||||
if re.search(r'sensor-group', line):
|
||||
resource_key = line.strip()
|
||||
cmd_ref['TMS_SENSORGROUP']['ref'].append(NxosCmdRef(self._module, TMS_SENSORGROUP))
|
||||
ref = cmd_ref['TMS_SENSORGROUP']['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
ref.get_existing(device_cache)
|
||||
|
||||
# Get Telemetry Subscription Data
|
||||
cmd_ref['TMS_SUBSCRIPTION']['ref'] = []
|
||||
for line in device_cache_lines:
|
||||
if re.search(r'subscription', line):
|
||||
resource_key = line.strip()
|
||||
cmd_ref['TMS_SUBSCRIPTION']['ref'].append(NxosCmdRef(self._module, TMS_SUBSCRIPTION))
|
||||
ref = cmd_ref['TMS_SUBSCRIPTION']['ref'][-1]
|
||||
ref.set_context([resource_key])
|
||||
ref.get_existing(device_cache)
|
||||
|
||||
objs = []
|
||||
objs = self.render_config(self.generated_spec, cmd_ref)
|
||||
facts = {'telemetry': {}}
|
||||
if objs:
|
||||
# params = utils.validate_config(self.argument_spec, {'config': objs})
|
||||
facts['telemetry'] = objs
|
||||
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
if self._module.params.get('state'):
|
||||
self._module.params['state'] = saved_module_state
|
||||
return ansible_facts
|
||||
|
||||
def render_config(self, spec, cmd_ref):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
config['destination_groups'] = []
|
||||
config['sensor_groups'] = []
|
||||
config['subscriptions'] = []
|
||||
managed_objects = ['TMS_GLOBAL', 'TMS_DESTGROUP', 'TMS_SENSORGROUP', 'TMS_SUBSCRIPTION']
|
||||
|
||||
# Walk the argspec and cmd_ref objects and build out config dict.
|
||||
for key in config.keys():
|
||||
for mo in managed_objects:
|
||||
for cr in cmd_ref[mo]['ref']:
|
||||
cr_keys = cr_key_lookup(key, mo)
|
||||
for cr_key in cr_keys:
|
||||
if cr._ref.get(cr_key) and cr._ref[cr_key].get('existing'):
|
||||
if isinstance(config[key], dict):
|
||||
for k in config[key].keys():
|
||||
for existing_key in cr._ref[cr_key]['existing'].keys():
|
||||
config[key][k] = cr._ref[cr_key]['existing'][existing_key][k]
|
||||
continue
|
||||
if isinstance(config[key], list):
|
||||
for existing_key in cr._ref[cr_key]['existing'].keys():
|
||||
data = get_instance_data(key, cr_key, cr, existing_key)
|
||||
config[key].append(data)
|
||||
continue
|
||||
for existing_key in cr._ref[cr_key]['existing'].keys():
|
||||
config[key] = cr._ref[cr_key]['existing'][existing_key]
|
||||
elif cr._ref.get(cr_key):
|
||||
data = get_instance_data(key, cr_key, cr, None)
|
||||
if isinstance(config[key], list) and data not in config[key]:
|
||||
config[key].append(data)
|
||||
|
||||
return utils.remove_empties(config)
|
@ -0,0 +1,181 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The nxos telemetry utility library
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def get_module_params_subsection(module_params, tms_config, resource_key=None):
|
||||
"""
|
||||
Helper method to get a specific module_params subsection
|
||||
"""
|
||||
mp = {}
|
||||
if tms_config == 'TMS_GLOBAL':
|
||||
relevant_keys = ['certificate',
|
||||
'compression',
|
||||
'source_interface',
|
||||
'vrf']
|
||||
for key in relevant_keys:
|
||||
mp[key] = module_params[key]
|
||||
|
||||
if tms_config == 'TMS_DESTGROUP':
|
||||
mp['destination_groups'] = []
|
||||
for destgrp in module_params['destination_groups']:
|
||||
if destgrp['id'] == resource_key:
|
||||
mp['destination_groups'].append(destgrp)
|
||||
|
||||
if tms_config == 'TMS_SENSORGROUP':
|
||||
mp['sensor_groups'] = []
|
||||
for sensor in module_params['sensor_groups']:
|
||||
if sensor['id'] == resource_key:
|
||||
mp['sensor_groups'].append(sensor)
|
||||
|
||||
if tms_config == 'TMS_SUBSCRIPTION':
|
||||
mp['subscriptions'] = []
|
||||
for sensor in module_params['subscriptions']:
|
||||
if sensor['id'] == resource_key:
|
||||
mp['subscriptions'].append(sensor)
|
||||
|
||||
return mp
|
||||
|
||||
|
||||
def valiate_input(playvals, type, module):
|
||||
"""
|
||||
Helper method to validate playbook values for destination groups
|
||||
"""
|
||||
if type == 'destination_groups':
|
||||
if not playvals.get('id'):
|
||||
msg = "Invalid playbook value: {0}.".format(playvals)
|
||||
msg += " Parameter <id> under <destination_groups> is required"
|
||||
module.fail_json(msg=msg)
|
||||
if playvals.get('destination') and not isinstance(playvals['destination'], dict):
|
||||
msg = "Invalid playbook value: {0}.".format(playvals)
|
||||
msg += " Parameter <destination> under <destination_groups> must be a dict"
|
||||
module.fail_json(msg=msg)
|
||||
if not playvals.get('destination') and len(playvals) > 1:
|
||||
msg = "Invalid playbook value: {0}.".format(playvals)
|
||||
msg += " Playbook entry contains unrecongnized parameters."
|
||||
msg += " Make sure <destination> keys under <destination_groups> are specified as follows:"
|
||||
msg += " destination: {ip: <ip>, port: <port>, protocol: <prot>, encoding: <enc>}}"
|
||||
module.fail_json(msg=msg)
|
||||
|
||||
if type == 'sensor_groups':
|
||||
if not playvals.get('id'):
|
||||
msg = "Invalid playbook value: {0}.".format(playvals)
|
||||
msg += " Parameter <id> under <sensor_groups> is required"
|
||||
module.fail_json(msg=msg)
|
||||
if playvals.get('path') and 'name' not in playvals['path'].keys():
|
||||
msg = "Invalid playbook value: {0}.".format(playvals)
|
||||
msg += " Parameter <path> under <sensor_groups> requires <name> key"
|
||||
module.fail_json(msg=msg)
|
||||
|
||||
|
||||
def get_instance_data(key, cr_key, cr, existing_key):
|
||||
"""
|
||||
Helper method to get instance data used to populate list structure in config
|
||||
fact dictionary
|
||||
"""
|
||||
data = {}
|
||||
if existing_key is None:
|
||||
instance = None
|
||||
else:
|
||||
instance = cr._ref[cr_key]['existing'][existing_key]
|
||||
|
||||
patterns = {
|
||||
'destination_groups': r"destination-group (\d+)",
|
||||
'sensor_groups': r"sensor-group (\d+)",
|
||||
'subscriptions': r"subscription (\d+)",
|
||||
}
|
||||
if key in patterns.keys():
|
||||
m = re.search(patterns[key], cr._ref['_resource_key'])
|
||||
instance_key = m.group(1)
|
||||
data = {'id': instance_key, cr_key: instance}
|
||||
|
||||
# Remove None values
|
||||
data = dict((k, v) for k, v in data.items() if v is not None)
|
||||
return data
|
||||
|
||||
|
||||
def cr_key_lookup(key, mo):
|
||||
"""
|
||||
Helper method to get instance key value for Managed Object (mo)
|
||||
"""
|
||||
cr_keys = [key]
|
||||
if key == 'destination_groups' and mo == 'TMS_DESTGROUP':
|
||||
cr_keys = ['destination']
|
||||
elif key == 'sensor_groups' and mo == 'TMS_SENSORGROUP':
|
||||
cr_keys = ['data_source', 'path']
|
||||
elif key == 'subscriptions' and mo == 'TMS_SUBSCRIPTION':
|
||||
cr_keys = ['destination_group', 'sensor_group']
|
||||
|
||||
return cr_keys
|
||||
|
||||
|
||||
def normalize_data(cmd_ref):
|
||||
''' Normalize playbook values and get_exisiting data '''
|
||||
|
||||
playval = cmd_ref._ref.get('destination').get('playval')
|
||||
existing = cmd_ref._ref.get('destination').get('existing')
|
||||
|
||||
dest_props = ['protocol', 'encoding']
|
||||
if playval:
|
||||
for prop in dest_props:
|
||||
for key in playval.keys():
|
||||
playval[key][prop] = playval[key][prop].lower()
|
||||
if existing:
|
||||
for key in existing.keys():
|
||||
for prop in dest_props:
|
||||
existing[key][prop] = existing[key][prop].lower()
|
||||
|
||||
|
||||
def remove_duplicate_context(cmds):
|
||||
''' Helper method to remove duplicate telemetry context commands '''
|
||||
if not cmds:
|
||||
return cmds
|
||||
feature_indices = [i for i, x in enumerate(cmds) if x == "feature telemetry"]
|
||||
telemetry_indices = [i for i, x in enumerate(cmds) if x == "telemetry"]
|
||||
if len(feature_indices) == 1 and len(telemetry_indices) == 1:
|
||||
return cmds
|
||||
if len(feature_indices) == 1 and not telemetry_indices:
|
||||
return cmds
|
||||
if len(telemetry_indices) == 1 and not feature_indices:
|
||||
return cmds
|
||||
if feature_indices and feature_indices[-1] > 1:
|
||||
cmds.pop(feature_indices[-1])
|
||||
return remove_duplicate_context(cmds)
|
||||
if telemetry_indices and telemetry_indices[-1] > 1:
|
||||
cmds.pop(telemetry_indices[-1])
|
||||
return remove_duplicate_context(cmds)
|
||||
|
||||
|
||||
def get_setval_path(module):
|
||||
''' Build setval for path parameter based on playbook inputs
|
||||
Full Command:
|
||||
- path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
|
||||
Required:
|
||||
- path {name}
|
||||
Optional:
|
||||
- depth {depth}
|
||||
- query-condition {query_condition},
|
||||
- filter-condition {filter_condition}
|
||||
'''
|
||||
path = module.params['config']['sensor_groups'][0].get('path')
|
||||
if path is None:
|
||||
return path
|
||||
|
||||
setval = 'path {name}'
|
||||
if 'depth' in path.keys():
|
||||
setval = setval + ' depth {depth}'
|
||||
if 'query_condition' in path.keys():
|
||||
setval = setval + ' query-condition {query_condition}'
|
||||
if 'filter_condition' in path.keys():
|
||||
setval = setval + ' filter-condition {filter_condition}'
|
||||
|
||||
return setval
|
@ -0,0 +1,333 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Cisco and/or its affiliates.
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#############################################
|
||||
# WARNING #
|
||||
#############################################
|
||||
#
|
||||
# This file is auto generated by the resource
|
||||
# module builder playbook.
|
||||
#
|
||||
# Do not edit this file manually.
|
||||
#
|
||||
# Changes to this file will be over written
|
||||
# by the resource module builder.
|
||||
#
|
||||
# Changes should be made in the model used to
|
||||
# generate this file or in the resource module
|
||||
# builder template.
|
||||
#
|
||||
#############################################
|
||||
|
||||
"""
|
||||
The module file for nxos_telemetry
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: nxos_telemetry
|
||||
version_added: 2.9
|
||||
short_description: 'Telemetry Monitoring Service (TMS) configuration'
|
||||
description: 'Manages Telemetry Monitoring Service (TMS) configuration'
|
||||
author: Mike Wiebe (@mikewiebe)
|
||||
notes:
|
||||
- 'Supported on N9k Version 7.0(3)I7(5) and later.'
|
||||
options:
|
||||
config:
|
||||
description: The provided configuration
|
||||
type: dict
|
||||
suboptions:
|
||||
certificate:
|
||||
type: dict
|
||||
description:
|
||||
- Certificate SSL/TLS and hostname values.
|
||||
- Value must be a dict defining values for keys (key and hostname).
|
||||
suboptions:
|
||||
key:
|
||||
description:
|
||||
- Certificate key
|
||||
type: str
|
||||
hostname:
|
||||
description:
|
||||
- Certificate hostname
|
||||
type: str
|
||||
compression:
|
||||
type: str
|
||||
description:
|
||||
- Destination profile compression method.
|
||||
choices:
|
||||
- gzip
|
||||
source_interface:
|
||||
type: str
|
||||
description:
|
||||
- Destination profile source interface.
|
||||
- Valid value is a str representing the source interface name.
|
||||
vrf:
|
||||
type: str
|
||||
description:
|
||||
- Destination profile vrf.
|
||||
- Valid value is a str representing the vrf name.
|
||||
destination_groups:
|
||||
type: list
|
||||
description:
|
||||
- List of telemetry destination groups.
|
||||
suboptions:
|
||||
id:
|
||||
type: int
|
||||
description:
|
||||
- Destination group identifier.
|
||||
- Value must be a int representing the destination group identifier.
|
||||
destination:
|
||||
type: dict
|
||||
description:
|
||||
- Group destination ipv4, port, protocol and encoding values.
|
||||
- Value must be a dict defining values for keys (ip, port, protocol, encoding).
|
||||
suboptions:
|
||||
ip:
|
||||
type: str
|
||||
description:
|
||||
- Destination group IP address.
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- Destination group port number.
|
||||
protocol:
|
||||
type: str
|
||||
description:
|
||||
- Destination group protocol.
|
||||
choices:
|
||||
- HTTP
|
||||
- TCP
|
||||
- UDP
|
||||
- gRPC
|
||||
encoding:
|
||||
type: str
|
||||
description:
|
||||
- Destination group encoding.
|
||||
choices:
|
||||
- GPB
|
||||
- JSON
|
||||
sensor_groups:
|
||||
type: list
|
||||
description:
|
||||
- List of telemetry sensor groups.
|
||||
suboptions:
|
||||
id:
|
||||
type: int
|
||||
description:
|
||||
- Sensor group identifier.
|
||||
- Value must be a int representing the sensor group identifier.
|
||||
data_source:
|
||||
type: str
|
||||
description:
|
||||
- Telemetry data source.
|
||||
choices:
|
||||
- NX-API
|
||||
- DME
|
||||
- YANG
|
||||
path:
|
||||
type: dict
|
||||
description:
|
||||
- Telemetry sensor path.
|
||||
- Value must be a dict defining values for keys (name, depth, filter_condition, query_condition).
|
||||
- Mandatory Keys (name)
|
||||
- Optional Keys (depth, filter_condition, query_condition)
|
||||
suboptions:
|
||||
name:
|
||||
type: str
|
||||
description:
|
||||
- Sensor group path name.
|
||||
depth:
|
||||
type: str
|
||||
description:
|
||||
- Sensor group depth.
|
||||
filter_condition:
|
||||
type: str
|
||||
description:
|
||||
- Sensor group filter condition.
|
||||
query_condition:
|
||||
type: str
|
||||
description:
|
||||
- Sensor group query condition.
|
||||
subscriptions:
|
||||
type: list
|
||||
description:
|
||||
- List of telemetry subscriptions.
|
||||
suboptions:
|
||||
id:
|
||||
type: int
|
||||
description:
|
||||
- Subscription identifier.
|
||||
- Value must be a int representing the subscription identifier.
|
||||
destination_group:
|
||||
type: int
|
||||
description:
|
||||
- Associated destination group.
|
||||
sensor_group:
|
||||
type: dict
|
||||
description:
|
||||
- Associated sensor group.
|
||||
- Value must be a dict defining values for keys (id, sample_interval).
|
||||
suboptions:
|
||||
id:
|
||||
type: int
|
||||
description:
|
||||
- Associated sensor group id.
|
||||
sample_interval:
|
||||
type: int
|
||||
description:
|
||||
- Associated sensor group id sample interval.
|
||||
|
||||
state:
|
||||
description:
|
||||
- Final configuration state
|
||||
type: str
|
||||
choices:
|
||||
- merged
|
||||
- replaced
|
||||
- deleted
|
||||
default: merged
|
||||
"""
|
||||
EXAMPLES = """
|
||||
# Using deleted
|
||||
# This action will delete all telemetry configuration on the device
|
||||
|
||||
- name: Delete Telemetry Configuration
|
||||
nxos_telemetry:
|
||||
state: deleted
|
||||
|
||||
|
||||
# Using merged
|
||||
# This action will merge telemetry configuration defined in the playbook with
|
||||
# telemetry configuration that is already on the device.
|
||||
|
||||
- name: Merge Telemetry Configuration
|
||||
nxos_telemetry:
|
||||
config:
|
||||
certificate:
|
||||
key: /bootflash/server.key
|
||||
hostname: localhost
|
||||
compression: gzip
|
||||
source_interface: Ethernet1/1
|
||||
vrf: management
|
||||
destination_groups:
|
||||
- id: 2
|
||||
destination:
|
||||
ip: 192.168.0.2
|
||||
port: 50001
|
||||
protocol: gPRC
|
||||
encoding: GPB
|
||||
- id: 55
|
||||
destination:
|
||||
ip: 192.168.0.55
|
||||
port: 60001
|
||||
protocol: gPRC
|
||||
encoding: GPB
|
||||
sensor_groups:
|
||||
- id: 1
|
||||
data_source: NX-API
|
||||
path:
|
||||
name: '"show lldp neighbors detail"'
|
||||
depth: 0
|
||||
- id: 55
|
||||
data_source: DME
|
||||
path:
|
||||
name: 'sys/ch'
|
||||
depth: unbounded
|
||||
filter_condition: 'ne(eqptFt.operSt,"ok")'
|
||||
subscriptions:
|
||||
- id: 5
|
||||
destination_group: 55
|
||||
sensor_group:
|
||||
id: 1
|
||||
sample_interval: 1000
|
||||
- id: 6
|
||||
destination_group: 2
|
||||
sensor_group:
|
||||
id: 55
|
||||
sample_interval: 2000
|
||||
state: merged
|
||||
|
||||
|
||||
# Using replaced
|
||||
# This action will replace telemetry configuration on the device with the
|
||||
# telmetry configuration defined in the playbook.
|
||||
|
||||
- name: Override Telemetry Configuration
|
||||
nxos_telemetry:
|
||||
config:
|
||||
certificate:
|
||||
key: /bootflash/server.key
|
||||
hostname: localhost
|
||||
compression: gzip
|
||||
source_interface: Ethernet1/1
|
||||
vrf: management
|
||||
destination_groups:
|
||||
- id: 2
|
||||
destination:
|
||||
ip: 192.168.0.2
|
||||
port: 50001
|
||||
protocol: gPRC
|
||||
encoding: GPB
|
||||
subscriptions:
|
||||
- id: 5
|
||||
destination_group: 55
|
||||
state: replaced
|
||||
|
||||
|
||||
"""
|
||||
RETURN = """
|
||||
before:
|
||||
description: The configuration prior to the model invocation.
|
||||
returned: always
|
||||
type: dict
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
after:
|
||||
description: The resulting configuration model invocation.
|
||||
returned: when changed
|
||||
type: dict
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
commands:
|
||||
description: The set of commands pushed to the remote device.
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['command 1', 'command 2', 'command 3']
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.nxos.argspec.telemetry.telemetry import TelemetryArgs
|
||||
from ansible.module_utils.network.nxos.config.telemetry.telemetry import Telemetry
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
module = AnsibleModule(argument_spec=TelemetryArgs.argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Telemetry(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,2 @@
|
||||
---
|
||||
testcase: "*"
|
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
- prepare_nxos_tests
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: collect common test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/common"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
connection: local
|
||||
register: test_cases
|
||||
|
||||
- name: collect cli test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
connection: local
|
||||
register: cli_cases
|
||||
|
||||
- set_fact:
|
||||
test_cases:
|
||||
files: "{{ test_cases.files }} + {{ cli_cases.files }}"
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test cases (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli connection={{ cli }}"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
- set_fact: run_test="true"
|
||||
|
||||
# Telemetry module only supported on N9k
|
||||
- set_fact: run_test="false"
|
||||
when: platform is not search("N9K")
|
||||
|
||||
# Telemetry module not supported on versions earlier then 7.0(3)I7(x)
|
||||
- set_fact: run_test="false"
|
||||
when: imagetag is search("I2|I3|I4|I5|I6")
|
||||
|
||||
- include: cli.yaml
|
||||
tags: 'cli'
|
||||
when: run_test
|
||||
# Uncomment below when nxapi is supported for resource module builder modules
|
||||
# - include: nxapi.yaml
|
||||
# tags: 'nxapi'
|
||||
# when: run_test
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: collect common test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/common"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
connection: local
|
||||
register: test_cases
|
||||
|
||||
- name: collect nxapi test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/nxapi"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
connection: local
|
||||
register: nxapi_cases
|
||||
|
||||
- set_fact:
|
||||
test_cases:
|
||||
files: "{{ test_cases.files }} + {{ nxapi_cases.files }}"
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test cases (connection=httpapi)
|
||||
include: "{{ test_case_to_run }} ansible_connection=httpapi connection={{ nxapi }}"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
@ -0,0 +1,93 @@
|
||||
---
|
||||
- debug: msg="START connection={{ ansible_connection }} nxos_telemetry deleted sanity test"
|
||||
|
||||
- set_fact: source_interface="Loopback55"
|
||||
when: imagetag and (major_version is version_compare('9.1', 'ge'))
|
||||
|
||||
- set_fact: before_keys_length=6
|
||||
- set_fact: before_keys_length=7
|
||||
when: imagetag and (major_version is version_compare('9.1', 'ge'))
|
||||
|
||||
- name: Setup
|
||||
nxos_feature: &setup_teardown
|
||||
feature: telemetry
|
||||
state: disabled
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Setup - Configure Telemetry
|
||||
nxos_telemetry:
|
||||
state: 'merged'
|
||||
config:
|
||||
certificate:
|
||||
key: /bootflash/server.key
|
||||
hostname: localhost
|
||||
compression: gzip
|
||||
source_interface: "{{source_interface|default(omit)}}"
|
||||
vrf: management
|
||||
destination_groups:
|
||||
- id: 2
|
||||
destination:
|
||||
ip: 192.168.0.1
|
||||
port: 50001
|
||||
protocol: grpc
|
||||
encoding: gpb
|
||||
- { id: 2, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: GPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.1, port: 50001, protocol: Grpc, encoding: gPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: gpb}}
|
||||
sensor_groups:
|
||||
- { id: 8, data_source: NX-API, path: {name: sys/bgp, depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 2, data_source: NX-API, path: {name: sys/bgp/inst, depth: unbounded, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: 'sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]', depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: sys/ospf, depth: 0, query_condition: foo, filter_condition: 'or(eq(ethpmPhysIf.operSt,"down"),eq(ethpmPhysIf.operSt,"up"))'}}
|
||||
subscriptions:
|
||||
- { id: 44, destination_group: 10, sensor_group: {id: 8, sample_interval: 2000}}
|
||||
- { id: 44, destination_group: 2, sensor_group: {id: 2, sample_interval: 2000}}
|
||||
- { id: 55, destination_group: 10, sensor_group: {id: 55, sample_interval: 2000}}
|
||||
|
||||
- block:
|
||||
- name: Gather Telemetry Facts Before Changes
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- '!all'
|
||||
- '!min'
|
||||
gather_network_resources:
|
||||
- telemetry
|
||||
|
||||
- name: Telemetry - deleted
|
||||
nxos_telemetry: &deleted
|
||||
state: 'deleted'
|
||||
register: result
|
||||
|
||||
# result.before|dict2items|length checks the number of dictionary keys.
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
- "'no telemetry' in result.commands"
|
||||
- "result.before|dict2items|length == {{ before_keys_length }}"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "(ansible_facts.network_resources.telemetry|dict2items)|symmetric_difference(result.before|dict2items)|length == 0"
|
||||
|
||||
- name: Gather Telemetry Facts After Changes
|
||||
nxos_facts: *facts
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "(ansible_facts.network_resources.telemetry|dict2items)|symmetric_difference(result.after|dict2items)|length == 0"
|
||||
|
||||
- name: Telemetry - deleted - idempotence
|
||||
nxos_telemetry: *deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "result.commands|length == 0"
|
||||
|
||||
always:
|
||||
- name: Teardown
|
||||
nxos_feature: *setup_teardown
|
||||
ignore_errors: yes
|
||||
|
||||
- debug: msg="END connection={{ ansible_connection }} nxos_telemetry deleted sanity test"
|
@ -0,0 +1,184 @@
|
||||
---
|
||||
- debug: msg="START connection={{ ansible_connection }} nxos_telemetry merged sanity test"
|
||||
|
||||
- set_fact: source_interface="Loopback55"
|
||||
when: imagetag and (major_version is version_compare('9.1', 'ge'))
|
||||
|
||||
- set_fact: command_list_length=30
|
||||
- set_fact: command_list_length=31
|
||||
when: imagetag and (major_version is version_compare('9.1', 'ge'))
|
||||
|
||||
- name: Setup
|
||||
nxos_feature: &setup_teardown
|
||||
feature: telemetry
|
||||
state: disabled
|
||||
ignore_errors: yes
|
||||
|
||||
- block:
|
||||
- name: Gather Telemetry Facts Before Changes
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- '!all'
|
||||
- '!min'
|
||||
gather_network_resources:
|
||||
- telemetry
|
||||
|
||||
- name: Telemetry - merged
|
||||
nxos_telemetry: &merged
|
||||
state: 'merged'
|
||||
config:
|
||||
certificate:
|
||||
key: /bootflash/server.key
|
||||
hostname: localhost
|
||||
compression: gzip
|
||||
source_interface: "{{source_interface|default(omit)}}"
|
||||
vrf: management
|
||||
destination_groups:
|
||||
- id: 2
|
||||
destination:
|
||||
ip: 192.168.0.1
|
||||
port: 50001
|
||||
protocol: grpc
|
||||
encoding: gpb
|
||||
- { id: 2, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: GPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.1, port: 50001, protocol: Grpc, encoding: gPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: gpb}}
|
||||
sensor_groups:
|
||||
- { id: 8, data_source: NX-API, path: {name: sys/bgp, depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 2, data_source: NX-API, path: {name: sys/bgp/inst, depth: unbounded, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: 'sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]', depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: sys/ospf, depth: 0, query_condition: foo, filter_condition: 'or(eq(ethpmPhysIf.operSt,"down"),eq(ethpmPhysIf.operSt,"up"))'}}
|
||||
subscriptions:
|
||||
- { id: 44, destination_group: 10, sensor_group: {id: 8, sample_interval: 2000}}
|
||||
- { id: 44, destination_group: 2, sensor_group: {id: 2, sample_interval: 2000}}
|
||||
- { id: 55, destination_group: 10, sensor_group: {id: 55, sample_interval: 2000}}
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
- "result.before|length == 0"
|
||||
- "'feature telemetry' in result.commands"
|
||||
- "'telemetry' in result.commands"
|
||||
- "'certificate /bootflash/server.key localhost' in result.commands"
|
||||
- "'destination-profile' in result.commands"
|
||||
- "'use-compression gzip' in result.commands"
|
||||
- "'use-vrf management' in result.commands"
|
||||
- "'destination-group 2' in result.commands"
|
||||
- "'ip address 192.168.0.1 port 50001 protocol grpc encoding gpb' in result.commands"
|
||||
- "'ip address 192.168.0.2 port 60001 protocol grpc encoding gpb' in result.commands"
|
||||
- "'destination-group 10' in result.commands"
|
||||
- "'ip address 192.168.0.1 port 50001 protocol grpc encoding gpb' in result.commands"
|
||||
- "'ip address 192.168.0.2 port 60001 protocol grpc encoding gpb' in result.commands"
|
||||
- "'sensor-group 8' in result.commands"
|
||||
- "'data-source NX-API' in result.commands"
|
||||
- "'path sys/bgp depth 0 query-condition foo filter-condition foo' in result.commands"
|
||||
- "'sensor-group 2' in result.commands"
|
||||
- "'data-source NX-API' in result.commands"
|
||||
- "'path sys/bgp/inst depth unbounded query-condition foo filter-condition foo' in result.commands"
|
||||
- "'sensor-group 55' in result.commands"
|
||||
- "'data-source DME' in result.commands"
|
||||
- "'path sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11] depth 0 query-condition foo filter-condition foo' in result.commands"
|
||||
- "'path sys/ospf depth 0 query-condition foo filter-condition or(eq(ethpmPhysIf.operSt,\"down\"),eq(ethpmPhysIf.operSt,\"up\"))' in result.commands"
|
||||
- "'subscription 44' in result.commands"
|
||||
- "'dst-grp 10' in result.commands"
|
||||
- "'dst-grp 2' in result.commands"
|
||||
- "'snsr-grp 8 sample-interval 2000' in result.commands"
|
||||
- "'snsr-grp 2 sample-interval 2000' in result.commands"
|
||||
- "'subscription 55' in result.commands"
|
||||
- "'dst-grp 10' in result.commands"
|
||||
- "'snsr-grp 55 sample-interval 2000' in result.commands"
|
||||
- "result.commands|length == {{ command_list_length }}"
|
||||
|
||||
# Source interface may or may not be included based on the image version.
|
||||
- assert:
|
||||
that:
|
||||
- "'source-interface loopback55' in result.commands"
|
||||
when: imagetag and (major_version is version_compare('9.1', 'ge'))
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "(ansible_facts.network_resources.telemetry|dict2items)|symmetric_difference(result.before|dict2items)|length == 0"
|
||||
|
||||
- name: Gather Telemetry Facts After Changes
|
||||
nxos_facts: *facts
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "(ansible_facts.network_resources.telemetry|dict2items)|symmetric_difference(result.after|dict2items)|length == 0"
|
||||
|
||||
- name: Telemetry - merged - idempotence
|
||||
nxos_telemetry: *merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "result.commands|length == 0"
|
||||
|
||||
- name: Telemetry - change values
|
||||
nxos_telemetry: &merged_change
|
||||
state: 'merged'
|
||||
config:
|
||||
certificate:
|
||||
key: /bootflash/local_server.key
|
||||
hostname: localhost
|
||||
compression: gzip
|
||||
source_interface: "{{source_interface|default(omit)}}"
|
||||
vrf: management
|
||||
destination_groups:
|
||||
- id: 2
|
||||
destination:
|
||||
ip: 192.168.0.1
|
||||
port: 50001
|
||||
protocol: grpc
|
||||
encoding: gpb
|
||||
- { id: 2, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: GPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.1, port: 50001, protocol: Grpc, encoding: gPB}}
|
||||
- { id: 10, destination: {ip: 192.168.0.2, port: 60001, protocol: gRPC, encoding: gpb}}
|
||||
sensor_groups:
|
||||
- { id: 8, data_source: NX-API, path: {name: sys/bgp, depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 2, data_source: NX-API, path: {name: sys/bgp/inst, depth: unbounded, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: 'sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]', depth: 0, query_condition: foo, filter_condition: foo}}
|
||||
- { id: 55, data_source: DME, path: {name: sys/ospf, depth: 0, query_condition: foo, filter_condition: 'or(eq(ethpmPhysIf.operSt,"down"),eq(ethpmPhysIf.operSt,"up"))'}}
|
||||
subscriptions:
|
||||
- { id: 44, destination_group: 10, sensor_group: {id: 8, sample_interval: 1000}}
|
||||
- { id: 44, destination_group: 2, sensor_group: {id: 2, sample_interval: 2000}}
|
||||
- { id: 55, destination_group: 10, sensor_group: {id: 55, sample_interval: 2000}}
|
||||
register: result
|
||||
|
||||
# The step above should result in only the following changes:
|
||||
# "commands": [
|
||||
# "telemetry",
|
||||
# "certificate /bootflash/local_server.key localhost",
|
||||
# "subscription 44",
|
||||
# "snsr-grp 8 sample-interval 1000"
|
||||
# ],
|
||||
|
||||
- set_fact:
|
||||
test_list:
|
||||
- "telemetry"
|
||||
- "certificate /bootflash/local_server.key localhost"
|
||||
- "subscription 44"
|
||||
- "snsr-grp 8 sample-interval 1000"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
- "test_list|symmetric_difference(result.commands)|length == 0"
|
||||
|
||||
- name: Telemetry - change values - idempotent
|
||||
nxos_telemetry: *merged_change
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "result.commands|length == 0"
|
||||
|
||||
always:
|
||||
- name: Teardown
|
||||
nxos_feature: *setup_teardown
|
||||
ignore_errors: yes
|
||||
|
||||
- debug: msg="END connection={{ ansible_connection }} nxos_telemetry merged sanity test"
|
@ -0,0 +1,43 @@
|
||||
feature telemetry
|
||||
|
||||
telemetry
|
||||
certificate /bootflash/server.key localhost
|
||||
destination-profile
|
||||
use-vrf management
|
||||
use-compression gzip
|
||||
source-interface loopback55
|
||||
destination-group 2
|
||||
ip address 192.168.0.1 port 50001 protocol gRPC encoding GPB
|
||||
ip address 192.168.0.2 port 60001 protocol gRPC encoding GPB
|
||||
destination-group 10
|
||||
ip address 192.168.0.1 port 50001 protocol gRPC encoding GPB
|
||||
ip address 192.168.0.2 port 60001 protocol gRPC encoding GPB
|
||||
sensor-group 2
|
||||
data-source DME
|
||||
path boo depth 0
|
||||
path sys/ospf depth 0 query-condition qc filter-condition fc
|
||||
path interfaces depth 0
|
||||
path sys/bgp
|
||||
path sys/bgp/inst depth 0 query-condition foo filter-condition foo
|
||||
path sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]
|
||||
path sys/bgp/inst/dom-default/peer-[20.20.20.11]/ent-[20.20.20.11]
|
||||
path too depth 0 filter-condition foo
|
||||
sensor-group 55
|
||||
sensor-group 56
|
||||
data-source DME
|
||||
path environment
|
||||
path interface
|
||||
path resources
|
||||
path vxlan
|
||||
subscription 3
|
||||
subscription 4
|
||||
dst-grp 2
|
||||
snsr-grp 2 sample-interval 1000
|
||||
subscription 5
|
||||
dst-grp 2
|
||||
snsr-grp 2 sample-interval 1000
|
||||
subscription 6
|
||||
dst-grp 10
|
||||
subscription 7
|
||||
dst-grp 10
|
||||
snsr-grp 2 sample-interval 1000
|
@ -0,0 +1,964 @@
|
||||
# (c) 2019 Red Hat Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from units.compat.mock import patch
|
||||
from units.modules.utils import AnsibleFailJson
|
||||
from ansible.modules.network.nxos import nxos_telemetry
|
||||
from ansible.module_utils.network.nxos.nxos import NxosCmdRef
|
||||
from ansible.module_utils.network.nxos.config.telemetry.telemetry import Telemetry
|
||||
from .nxos_module import TestNxosModule, load_fixture, set_module_args
|
||||
|
||||
# TBD: These imports / import checks are only needed as a workaround for
|
||||
# shippable, which fails this test due to import yaml & import ordereddict.
|
||||
import pytest
|
||||
from ansible.module_utils.network.nxos.nxos import nxosCmdRef_import_check
|
||||
msg = nxosCmdRef_import_check()
|
||||
ignore_provider_arg = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(len(msg), reason=msg)
|
||||
class TestNxosTelemetryModule(TestNxosModule):
|
||||
|
||||
module = nxos_telemetry
|
||||
|
||||
def setUp(self):
|
||||
super(TestNxosTelemetryModule, self).setUp()
|
||||
|
||||
self.mock_FACT_LEGACY_SUBSETS = patch('ansible.module_utils.network.nxos.facts.facts.FACT_LEGACY_SUBSETS')
|
||||
self.FACT_LEGACY_SUBSETS = self.mock_FACT_LEGACY_SUBSETS.start()
|
||||
|
||||
self.mock_get_resource_connection_config = patch('ansible.module_utils.network.common.cfg.base.get_resource_connection')
|
||||
self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
|
||||
|
||||
self.mock_get_resource_connection_facts = patch('ansible.module_utils.network.common.facts.facts.get_resource_connection')
|
||||
self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
|
||||
|
||||
self.mock_edit_config = patch('ansible.module_utils.network.nxos.config.telemetry.telemetry.Telemetry.edit_config')
|
||||
self.edit_config = self.mock_edit_config.start()
|
||||
|
||||
self.mock_execute_show_command = patch('ansible.module_utils.network.nxos.nxos.NxosCmdRef.execute_show_command')
|
||||
self.execute_show_command = self.mock_execute_show_command.start()
|
||||
|
||||
self.mock_get_platform_shortname = patch('ansible.module_utils.network.nxos.nxos.NxosCmdRef.get_platform_shortname')
|
||||
self.get_platform_shortname = self.mock_get_platform_shortname.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestNxosTelemetryModule, self).tearDown()
|
||||
self.mock_FACT_LEGACY_SUBSETS.stop()
|
||||
self.mock_get_resource_connection_config.stop()
|
||||
self.mock_get_resource_connection_facts.stop()
|
||||
self.mock_edit_config.stop()
|
||||
self.mock_execute_show_command.stop()
|
||||
self.get_platform_shortname.stop()
|
||||
|
||||
def load_fixtures(self, commands=None, device=''):
|
||||
self.mock_FACT_LEGACY_SUBSETS.return_value = dict()
|
||||
self.get_resource_connection_config.return_value = 'Connection'
|
||||
self.get_resource_connection_facts.return_value = 'Connection'
|
||||
self.edit_config.return_value = None
|
||||
|
||||
# ---------------------------
|
||||
# Telemetry Global Test Cases
|
||||
# ---------------------------
|
||||
|
||||
def test_tms_global_merged_n9k(self):
|
||||
# Assumes feature telemetry is disabled
|
||||
# TMS global config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/sample.key', 'hostname': 'server.example.com'},
|
||||
compression='gzip',
|
||||
source_interface='Ethernet2/1',
|
||||
vrf='blue',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'certificate /bootflash/sample.key server.example.com',
|
||||
'destination-profile',
|
||||
'use-compression gzip',
|
||||
'source-interface Ethernet2/1',
|
||||
'use-vrf blue'
|
||||
])
|
||||
|
||||
def test_tms_global_checkmode_n9k(self):
|
||||
# Assumes feature telemetry is disabled
|
||||
# TMS global config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
_ansible_check_mode=True,
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/sample.key', 'hostname': 'server.example.com'},
|
||||
compression='gzip',
|
||||
source_interface='Ethernet2/1',
|
||||
vrf='blue',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'certificate /bootflash/sample.key server.example.com',
|
||||
'destination-profile',
|
||||
'use-compression gzip',
|
||||
'source-interface Ethernet2/1',
|
||||
'use-vrf blue'
|
||||
])
|
||||
|
||||
def test_tms_global_merged2_n9k(self):
|
||||
# Assumes feature telemetry is disabled
|
||||
# TMS global config is not present.
|
||||
# Configure only vrf
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
vrf='blue',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'destination-profile',
|
||||
'use-vrf blue'
|
||||
])
|
||||
|
||||
def test_tms_global_idempotent_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/server.key', 'hostname': 'localhost'},
|
||||
compression='gzip',
|
||||
source_interface='loopback55',
|
||||
vrf='management',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_global_change_cert_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present
|
||||
# Change certificate
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/server.key', 'hostname': 'my_host'},
|
||||
compression='gzip',
|
||||
source_interface='loopback55',
|
||||
vrf='management',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'certificate /bootflash/server.key my_host'
|
||||
])
|
||||
|
||||
def test_tms_global_change_interface_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present
|
||||
# Change interface
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/server.key', 'hostname': 'localhost'},
|
||||
compression='gzip',
|
||||
source_interface='Ethernet8/1',
|
||||
vrf='management',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'destination-profile',
|
||||
'source-interface Ethernet8/1'
|
||||
])
|
||||
|
||||
def test_tms_global_change_several_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present
|
||||
# Change source_interface, vrf and cert
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/server_5.key', 'hostname': 'my_host'},
|
||||
compression='gzip',
|
||||
source_interface='Ethernet8/1',
|
||||
vrf='blue',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'certificate /bootflash/server_5.key my_host',
|
||||
'destination-profile',
|
||||
'source-interface Ethernet8/1',
|
||||
'use-vrf blue',
|
||||
])
|
||||
|
||||
# ------------------------------
|
||||
# Telemetry DestGroup Test Cases
|
||||
# ------------------------------
|
||||
|
||||
def test_tms_destgroup_input_validation_1(self):
|
||||
# Mandatory parameter 'id' missing.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'}}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert 'Parameter <id> under <destination_groups> is required' in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_tms_destgroup_input_validation_2(self):
|
||||
# Parameter 'destination' is not a dict.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '88',
|
||||
'destination': '192.168.1.1',
|
||||
}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert "Parameter <destination> under <destination_groups> must be a dict" in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_tms_destgroup_input_validation_3(self):
|
||||
# Parameter 'destination' is not a dict.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '88',
|
||||
'ip': '192.168.1.1',
|
||||
'port': '5001'
|
||||
}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert 'Playbook entry contains unrecongnized parameters' in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_tms_destgroup_merged_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '88',
|
||||
'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '88',
|
||||
'destination': {'ip': '192.168.1.2', 'port': '6001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '99',
|
||||
'destination': {'ip': '192.168.1.2', 'port': '6001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '99',
|
||||
'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'destination-group 88',
|
||||
'ip address 192.168.1.1 port 5001 protocol grpc encoding gpb',
|
||||
'ip address 192.168.1.2 port 6001 protocol grpc encoding gpb',
|
||||
'destination-group 99',
|
||||
'ip address 192.168.1.2 port 6001 protocol grpc encoding gpb',
|
||||
'ip address 192.168.1.1 port 5001 protocol grpc encoding gpb',
|
||||
])
|
||||
|
||||
def test_tms_destgroup_checkmode_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '88',
|
||||
'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
}
|
||||
], 'destination_groups', state='merged', check_mode=True)
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'destination-group 88',
|
||||
'ip address 192.168.1.1 port 5001 protocol grpc encoding gpb'
|
||||
])
|
||||
|
||||
def test_tms_destgroup_merged2_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is not present.
|
||||
# Configure only identifier
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '88'}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'destination-group 88',
|
||||
])
|
||||
|
||||
def test_tms_destgroup_idempotent_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is not present.
|
||||
# Configure only identifier
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'destination': {'ip': '192.168.0.2', 'port': '60001', 'protocol': 'grpc', 'encoding': 'gpb'},
|
||||
}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_destgroup_idempotent2_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is not present.
|
||||
# Configure only identifier
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2'}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_destgroup_merged_aggregate_idempotent_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS destgroup config is present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'destination': {'ip': '192.168.0.1', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
},
|
||||
{'id': '10',
|
||||
'destination': {'ip': '192.168.0.1', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_destgroup_change_n9k(self):
|
||||
# TMS destgroup config is not present.
|
||||
# Change protocol and encoding for dest group 2
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'destination': {'ip': '192.168.0.1', 'port': '50001', 'protocol': 'http', 'encoding': 'JSON'}
|
||||
},
|
||||
{'id': '10',
|
||||
'destination': {'ip': '192.168.0.1', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
}
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry', 'destination-group 2',
|
||||
'ip address 192.168.0.1 port 50001 protocol http encoding json'
|
||||
])
|
||||
|
||||
def test_tms_destgroup_add_n9k(self):
|
||||
# TMS destgroup config is not present.
|
||||
# Add destinations to destgroup 10
|
||||
# Add new destgroup 55 and 56
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '10',
|
||||
'destination': {'ip': '192.168.0.1', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
},
|
||||
{'id': '10',
|
||||
'destination': {'ip': '192.168.0.10', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
},
|
||||
{'id': '55',
|
||||
'destination': {'ip': '192.168.0.2', 'port': '50001', 'protocol': 'gRPC', 'encoding': 'gpb'}
|
||||
},
|
||||
{'id': '56'},
|
||||
], 'destination_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'destination-group 10',
|
||||
'ip address 192.168.0.10 port 50001 protocol grpc encoding gpb',
|
||||
'destination-group 55',
|
||||
'ip address 192.168.0.2 port 50001 protocol grpc encoding gpb',
|
||||
'destination-group 56'
|
||||
])
|
||||
|
||||
# --------------------------------
|
||||
# Telemetry SensorGroup Test Cases
|
||||
# --------------------------------
|
||||
|
||||
def test_tms_sensorgroup_merged_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS sensorgroup config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
td55_name = 'sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]'
|
||||
td55_fc = 'or(eq(ethpmPhysIf.operSt,"down"),eq(ethpmPhysIf.operSt,"up"))'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'data_source': 'NX-API',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'query_condition': 'foo', 'filter_condition': 'foo'},
|
||||
},
|
||||
{'id': '2',
|
||||
'data_source': 'NX-API',
|
||||
'path': {'name': 'sys/bgp/inst', 'depth': 'unbounded', 'query_condition': 'foo', 'filter_condition': 'foo'},
|
||||
},
|
||||
{'id': '55',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': td55_name, 'depth': 0, 'query_condition': 'foo', 'filter_condition': 'foo'},
|
||||
},
|
||||
{'id': '55',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/ospf', 'depth': 0, 'query_condition': 'foo', 'filter_condition': td55_fc},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 2',
|
||||
'data-source NX-API',
|
||||
'path sys/bgp depth 0 query-condition foo filter-condition foo',
|
||||
'path sys/bgp/inst depth unbounded query-condition foo filter-condition foo',
|
||||
'sensor-group 55',
|
||||
'data-source DME',
|
||||
'path sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11] depth 0 query-condition foo filter-condition foo',
|
||||
'path sys/ospf depth 0 query-condition foo filter-condition or(eq(ethpmPhysIf.operSt,"down"),eq(ethpmPhysIf.operSt,"up"))',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_input_validation_1(self):
|
||||
# Mandatory parameter 'id' missing.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'query_condition': 'query_condition_xyz', 'filter_condition': 'filter_condition_xyz'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert 'Parameter <id> under <sensor_groups> is required' in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_tms_sensorgroup_input_validation_2(self):
|
||||
# Path present but mandatory 'name' key is not
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'depth': 0, 'query_condition': 'query_condition_xyz', 'filter_condition': 'filter_condition_xyz'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert 'Parameter <path> under <sensor_groups> requires <name> key' in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_tms_sensorgroup_resource_key_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77'}
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_merged_variable_args1_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Only path key name provided
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source DME',
|
||||
'path sys/bgp',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_merged_variable_args2_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Only path keys name and depth provided
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source DME',
|
||||
'path sys/bgp depth 0',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_merged_variable_args3_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Only path keys name, depth and query_condition provided
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'query_condition': 'query_condition_xyz'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source DME',
|
||||
'path sys/bgp depth 0 query-condition query_condition_xyz',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_merged_variable_args4_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Only path keys name, depth and filter_condition provided
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'filter_condition': 'filter_condition_xyz'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source DME',
|
||||
'path sys/bgp depth 0 filter-condition filter_condition_xyz',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_merged_idempotent_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS sensorgroup config is not present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/ospf', 'depth': 0, 'query_condition': 'qc', 'filter_condition': 'fc'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_sensorgroup_vxlan_idempotent_n9k(self):
|
||||
# TMS sensorgroup config present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '56',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'vxlan'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_sensorgroup_idempotent_variable1_n9k(self):
|
||||
# TMS sensorgroup config is present with path key name.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp/inst/dom-default/peer-[10.10.10.11]/ent-[10.10.10.11]'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_sensorgroup_idempotent_variable2_n9k(self):
|
||||
# TMS sensorgroup config is present with path key name and depth.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '2',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'boo', 'depth': 0},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_sensorgroup_idempotent_resource_key_n9k(self):
|
||||
# TMS sensorgroup config is present resource key only.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '55'}
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_sensorgroup_present_path_environment_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Path name 'environment' test
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'YANG',
|
||||
'path': {'name': 'environment'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source YANG',
|
||||
'path environment',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_present_path_interface_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Path name 'interface' test
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'NATIVE',
|
||||
'path': {'name': 'interface'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source NATIVE',
|
||||
'path interface',
|
||||
])
|
||||
|
||||
def test_tms_sensorgroup_present_path_interface_n9k(self):
|
||||
# TMS sensorgroup config is not present.
|
||||
# Path name 'resources' test
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': '77',
|
||||
'data_source': 'NX-API',
|
||||
'path': {'name': 'resources'},
|
||||
},
|
||||
], 'sensor_groups')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'sensor-group 77',
|
||||
'data-source NX-API',
|
||||
'path resources',
|
||||
])
|
||||
|
||||
# ---------------------------------
|
||||
# Telemetry Subscription Test Cases
|
||||
# ---------------------------------
|
||||
|
||||
def test_tms_subscription_merged_n9k(self):
|
||||
# TMS subscription config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': 5,
|
||||
'destination_group': 55,
|
||||
'sensor_group': {'id': 1, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 88,
|
||||
'destination_group': 3,
|
||||
'sensor_group': {'id': 4, 'sample_interval': 2000},
|
||||
},
|
||||
], 'subscriptions')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'subscription 5',
|
||||
'dst-grp 55',
|
||||
'snsr-grp 1 sample-interval 1000',
|
||||
'subscription 88',
|
||||
'dst-grp 3',
|
||||
'snsr-grp 4 sample-interval 2000'
|
||||
])
|
||||
|
||||
def test_tms_subscription_merged_idempotent_n9k(self):
|
||||
# TMS subscription config is not present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': 3,
|
||||
},
|
||||
{'id': 7,
|
||||
'destination_group': 10,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 2,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
], 'subscriptions')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_tms_subscription_merged_change1_n9k(self):
|
||||
# TMS subscription config present.
|
||||
# Change sample interval for sensor group 2
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': 3,
|
||||
},
|
||||
{'id': 7,
|
||||
'destination_group': 10,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 3000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 2,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
], 'subscriptions')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'subscription 7',
|
||||
'snsr-grp 2 sample-interval 3000'
|
||||
])
|
||||
|
||||
def test_tms_subscription_add_n9k(self):
|
||||
# TMS subscription config present.
|
||||
# Add new destination_group and sensor_group to subscription 5
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
args = build_args([
|
||||
{'id': 3,
|
||||
},
|
||||
{'id': 7,
|
||||
'destination_group': 10,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 2,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 7,
|
||||
'sensor_group': {'id': 2, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 8,
|
||||
'sensor_group': {'id': 9, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 5,
|
||||
'destination_group': 9,
|
||||
'sensor_group': {'id': 10, 'sample_interval': 1000},
|
||||
},
|
||||
], 'subscriptions')
|
||||
set_module_args(args, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'telemetry',
|
||||
'subscription 5',
|
||||
'dst-grp 7',
|
||||
'dst-grp 8',
|
||||
'dst-grp 9',
|
||||
'snsr-grp 9 sample-interval 1000',
|
||||
'snsr-grp 10 sample-interval 1000'
|
||||
])
|
||||
|
||||
def test_telemetry_full_n9k(self):
|
||||
# Assumes feature telemetry is disabled
|
||||
# TMS global config is not present.
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args({
|
||||
'state': 'merged',
|
||||
'config': {
|
||||
'certificate': {'key': '/bootflash/sample.key', 'hostname': 'server.example.com'},
|
||||
'compression': 'gzip',
|
||||
'source_interface': 'Ethernet2/1',
|
||||
'vrf': 'blue',
|
||||
'destination_groups': [
|
||||
{'id': '88',
|
||||
'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '88',
|
||||
'destination': {'ip': '192.168.1.2', 'port': '6001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '99',
|
||||
'destination': {'ip': '192.168.1.2', 'port': '6001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
{'id': '99',
|
||||
'destination': {'ip': '192.168.1.1', 'port': '5001', 'protocol': 'GRPC', 'encoding': 'GPB'},
|
||||
},
|
||||
],
|
||||
'sensor_groups': [
|
||||
{'id': '77',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'query_condition': 'query_condition_xyz', 'filter_condition': 'filter_condition_xyz'},
|
||||
},
|
||||
{'id': '99',
|
||||
'data_source': 'DME',
|
||||
'path': {'name': 'sys/bgp', 'depth': 0, 'query_condition': 'query_condition_xyz', 'filter_condition': 'filter_condition_xyz'},
|
||||
},
|
||||
],
|
||||
'subscriptions': [
|
||||
{'id': 5,
|
||||
'destination_group': 55,
|
||||
'sensor_group': {'id': 1, 'sample_interval': 1000},
|
||||
},
|
||||
{'id': 88,
|
||||
'destination_group': 3,
|
||||
'sensor_group': {'id': 4, 'sample_interval': 2000},
|
||||
},
|
||||
],
|
||||
}
|
||||
}, ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=[
|
||||
'feature telemetry',
|
||||
'telemetry',
|
||||
'certificate /bootflash/sample.key server.example.com',
|
||||
'destination-profile',
|
||||
'use-compression gzip',
|
||||
'source-interface Ethernet2/1',
|
||||
'use-vrf blue',
|
||||
'destination-group 88',
|
||||
'ip address 192.168.1.1 port 5001 protocol grpc encoding gpb',
|
||||
'ip address 192.168.1.2 port 6001 protocol grpc encoding gpb',
|
||||
'destination-group 99',
|
||||
'ip address 192.168.1.2 port 6001 protocol grpc encoding gpb',
|
||||
'ip address 192.168.1.1 port 5001 protocol grpc encoding gpb',
|
||||
'sensor-group 77',
|
||||
'data-source DME',
|
||||
'path sys/bgp depth 0 query-condition query_condition_xyz filter-condition filter_condition_xyz',
|
||||
'sensor-group 99',
|
||||
'data-source DME',
|
||||
'path sys/bgp depth 0 query-condition query_condition_xyz filter-condition filter_condition_xyz',
|
||||
'subscription 5',
|
||||
'dst-grp 55',
|
||||
'snsr-grp 1 sample-interval 1000',
|
||||
'subscription 88',
|
||||
'dst-grp 3',
|
||||
'snsr-grp 4 sample-interval 2000'
|
||||
])
|
||||
|
||||
def test_telemetry_deleted_input_validation_n9k(self):
|
||||
# State is 'deleted' and 'config' key present.
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
state='deleted',
|
||||
config=dict(
|
||||
certificate={'key': '/bootflash/server.key', 'hostname': 'localhost'},
|
||||
compression='gzip',
|
||||
source_interface='loopback55',
|
||||
vrf='management',
|
||||
)
|
||||
), ignore_provider_arg)
|
||||
with pytest.raises(AnsibleFailJson) as errinfo:
|
||||
self.execute_module()
|
||||
testdata = errinfo.value.args[0]
|
||||
assert 'Remove config key from playbook when state is <deleted>' in str(testdata['msg'])
|
||||
assert testdata['failed']
|
||||
|
||||
def test_telemetry_deleted_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present.
|
||||
# Make absent with all playbook keys provided
|
||||
self.execute_show_command.return_value = load_fixture('nxos_telemetry', 'N9K.cfg')
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
state='deleted',
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=True, commands=['no telemetry'])
|
||||
|
||||
def test_telemetry_deleted_idempotent_n9k(self):
|
||||
# Assumes feature telemetry is enabled
|
||||
# TMS global config is present.
|
||||
# Make absent with all playbook keys provided
|
||||
self.execute_show_command.return_value = None
|
||||
self.get_platform_shortname.return_value = 'N9K'
|
||||
set_module_args(dict(
|
||||
state='deleted',
|
||||
), ignore_provider_arg)
|
||||
self.execute_module(changed=False)
|
||||
|
||||
|
||||
def build_args(data, type, state=None, check_mode=None):
|
||||
if state is None:
|
||||
state = 'merged'
|
||||
if check_mode is None:
|
||||
check_mode = False
|
||||
args = {
|
||||
'state': state,
|
||||
'_ansible_check_mode': check_mode,
|
||||
'config': {
|
||||
type: data
|
||||
}
|
||||
}
|
||||
return args
|
Loading…
Reference in New Issue