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

1168 lines
46 KiB
Python
Raw Normal View History

2020-03-09 10:11:07 +01:00
#!/usr/bin/python
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ce_sflow
short_description: Manages sFlow configuration on HUAWEI CloudEngine switches.
description:
- Configure Sampled Flow (sFlow) to monitor traffic on an interface in real time,
detect abnormal traffic, and locate the source of attack traffic,
ensuring stable running of the network.
author: QijunPan (@QijunPan)
notes:
- This module requires the netconf system service be enabled on the remote device being managed.
- Recommended connection is C(netconf).
- This module also works with C(local) connections for legacy playbooks.
options:
agent_ip:
description:
- Specifies the IPv4/IPv6 address of an sFlow agent.
source_ip:
description:
- Specifies the source IPv4/IPv6 address of sFlow packets.
collector_id:
description:
- Specifies the ID of an sFlow collector. This ID is used when you specify
the collector in subsequent sFlow configuration.
choices: ['1', '2']
collector_ip:
description:
- Specifies the IPv4/IPv6 address of the sFlow collector.
collector_ip_vpn:
description:
- Specifies the name of a VPN instance.
The value is a string of 1 to 31 case-sensitive characters, spaces not supported.
When double quotation marks are used around the string, spaces are allowed in the string.
The value C(_public_) is reserved and cannot be used as the VPN instance name.
collector_datagram_size:
description:
- Specifies the maximum length of sFlow packets sent from an sFlow agent to an sFlow collector.
The value is an integer, in bytes. It ranges from 1024 to 8100. The default value is 1400.
collector_udp_port:
description:
- Specifies the UDP destination port number of sFlow packets.
The value is an integer that ranges from 1 to 65535. The default value is 6343.
collector_meth:
description:
- Configures the device to send sFlow packets through service interfaces,
enhancing the sFlow packet forwarding capability.
The enhanced parameter is optional. No matter whether you configure the enhanced mode,
the switch determines to send sFlow packets through service cards or management port
based on the routing information on the collector.
When the value is meth, the device forwards sFlow packets at the control plane.
When the value is enhanced, the device forwards sFlow packets at the forwarding plane to
enhance the sFlow packet forwarding capacity.
choices: ['meth', 'enhanced']
collector_description:
description:
- Specifies the description of an sFlow collector.
The value is a string of 1 to 255 case-sensitive characters without spaces.
sflow_interface:
description:
- Full name of interface for Flow Sampling or Counter.
It must be a physical interface, Eth-Trunk, or Layer 2 subinterface.
sample_collector:
description:
- Indicates the ID list of the collector.
sample_rate:
description:
- Specifies the flow sampling rate in the format 1/rate.
The value is an integer and ranges from 1 to 4294967295. The default value is 8192.
sample_length:
description:
- Specifies the maximum length of sampled packets.
The value is an integer and ranges from 18 to 512, in bytes. The default value is 128.
sample_direction:
description:
- Enables flow sampling in the inbound or outbound direction.
choices: ['inbound', 'outbound', 'both']
counter_collector:
description:
- Indicates the ID list of the counter collector.
counter_interval:
description:
- Indicates the counter sampling interval.
The value is an integer that ranges from 10 to 4294967295, in seconds. The default value is 20.
export_route:
description:
- Configures the sFlow packets sent by the switch not to carry routing information.
choices: ['enable', 'disable']
rate_limit:
description:
- Specifies the rate of sFlow packets sent from a card to the control plane.
The value is an integer that ranges from 100 to 1500, in pps.
type: str
rate_limit_slot:
description:
- Specifies the slot where the rate of output sFlow packets is limited.
If this parameter is not specified, the rate of sFlow packets sent from
all cards to the control plane is limited.
The value is an integer or a string of characters.
type: str
forward_enp_slot:
description:
- Enable the Embedded Network Processor (ENP) chip function.
The switch uses the ENP chip to perform sFlow sampling,
and the maximum sFlow sampling interval is 65535.
If you set the sampling interval to be larger than 65535,
the switch automatically restores it to 65535.
The value is an integer or 'all'.
type: str
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = '''
---
- name: sflow module test
hosts: ce128
connection: local
gather_facts: no
vars:
cli:
host: "{{ inventory_hostname }}"
port: "{{ ansible_ssh_port }}"
username: "{{ username }}"
password: "{{ password }}"
transport: cli
tasks:
- name: Configuring sFlow Agent
ce_sflow:
agent_ip: 6.6.6.6
provider: '{{ cli }}'
- name: Configuring sFlow Collector
ce_sflow:
collector_id: 1
collector_ip: 7.7.7.7
collector_ip_vpn: vpn1
collector_description: Collector1
provider: '{{ cli }}'
- name: Configure flow sampling.
ce_sflow:
sflow_interface: 10GE2/0/2
sample_collector: 1
sample_direction: inbound
provider: '{{ cli }}'
- name: Configure counter sampling.
ce_sflow:
sflow_interface: 10GE2/0/2
counter_collector: 1
counter_interval: 1000
provider: '{{ cli }}'
'''
RETURN = '''
proposed:
description: k/v pairs of parameters passed into module
returned: verbose mode
type: dict
sample: {"agent_ip": "6.6.6.6", "state": "present"}
existing:
description: k/v pairs of existing configuration
returned: verbose mode
type: dict
sample: {"agent": {}}
end_state:
description: k/v pairs of configuration after module execution
returned: verbose mode
type: dict
sample: {"agent": {"family": "ipv4", "ipv4Addr": "1.2.3.4", "ipv6Addr": null}}
updates:
description: commands sent to the device
returned: always
type: list
sample: ["sflow agent ip 6.6.6.6"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
import re
from xml.etree import ElementTree
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec, check_ip_addr
CE_NC_GET_SFLOW = """
<filter type="subtree">
<sflow xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
<sources>
<source>
<family></family>
<ipv4Addr></ipv4Addr>
<ipv6Addr></ipv6Addr>
</source>
</sources>
<agents>
<agent>
<family></family>
<ipv4Addr></ipv4Addr>
<ipv6Addr></ipv6Addr>
</agent>
</agents>
<collectors>
<collector>
<collectorID></collectorID>
<family></family>
<ipv4Addr></ipv4Addr>
<ipv6Addr></ipv6Addr>
<vrfName></vrfName>
<datagramSize></datagramSize>
<port></port>
<description></description>
<meth></meth>
</collector>
</collectors>
<samplings>
<sampling>
<ifName>%s</ifName>
<collectorID></collectorID>
<direction></direction>
<length></length>
<rate></rate>
</sampling>
</samplings>
<counters>
<counter>
<ifName>%s</ifName>
<collectorID></collectorID>
<interval></interval>
</counter>
</counters>
<exports>
<export>
<ExportRoute></ExportRoute>
</export>
</exports>
</sflow>
</filter>
"""
def is_config_exist(cmp_cfg, test_cfg):
"""is configuration exist?"""
if not cmp_cfg or not test_cfg:
return False
return bool(test_cfg in cmp_cfg)
def is_valid_ip_vpn(vpname):
"""check ip vpn"""
if not vpname:
return False
if vpname == "_public_":
return False
if len(vpname) < 1 or len(vpname) > 31:
return False
return True
def get_ip_version(address):
"""get ip version fast"""
if not address:
return None
if address.count(':') >= 2 and address.count(":") <= 7:
return "ipv6"
elif address.count('.') == 3:
return "ipv4"
else:
return None
def get_interface_type(interface):
"""get the type of interface, such as 10GE, ETH-TRUNK, VLANIF..."""
if interface is None:
return None
if interface.upper().startswith('GE'):
iftype = 'ge'
elif interface.upper().startswith('10GE'):
iftype = '10ge'
elif interface.upper().startswith('25GE'):
iftype = '25ge'
elif interface.upper().startswith('4X10GE'):
iftype = '4x10ge'
elif interface.upper().startswith('40GE'):
iftype = '40ge'
elif interface.upper().startswith('100GE'):
iftype = '100ge'
elif interface.upper().startswith('VLANIF'):
iftype = 'vlanif'
elif interface.upper().startswith('LOOPBACK'):
iftype = 'loopback'
elif interface.upper().startswith('METH'):
iftype = 'meth'
elif interface.upper().startswith('ETH-TRUNK'):
iftype = 'eth-trunk'
elif interface.upper().startswith('VBDIF'):
iftype = 'vbdif'
elif interface.upper().startswith('NVE'):
iftype = 'nve'
elif interface.upper().startswith('TUNNEL'):
iftype = 'tunnel'
elif interface.upper().startswith('ETHERNET'):
iftype = 'ethernet'
elif interface.upper().startswith('FCOE-PORT'):
iftype = 'fcoe-port'
elif interface.upper().startswith('FABRIC-PORT'):
iftype = 'fabric-port'
elif interface.upper().startswith('STACK-PORT'):
iftype = 'stack-port'
elif interface.upper().startswith('NULL'):
iftype = 'null'
else:
return None
return iftype.lower()
class Sflow(object):
"""Manages sFlow"""
def __init__(self, argument_spec):
self.spec = argument_spec
self.module = None
self.__init_module__()
# module input info
self.agent_ip = self.module.params['agent_ip']
self.agent_version = None
self.source_ip = self.module.params['source_ip']
self.source_version = None
self.export_route = self.module.params['export_route']
self.rate_limit = self.module.params['rate_limit']
self.rate_limit_slot = self.module.params['rate_limit_slot']
self.forward_enp_slot = self.module.params['forward_enp_slot']
self.collector_id = self.module.params['collector_id']
self.collector_ip = self.module.params['collector_ip']
self.collector_version = None
self.collector_ip_vpn = self.module.params['collector_ip_vpn']
self.collector_datagram_size = self.module.params['collector_datagram_size']
self.collector_udp_port = self.module.params['collector_udp_port']
self.collector_meth = self.module.params['collector_meth']
self.collector_description = self.module.params['collector_description']
self.sflow_interface = self.module.params['sflow_interface']
self.sample_collector = self.module.params['sample_collector'] or list()
self.sample_rate = self.module.params['sample_rate']
self.sample_length = self.module.params['sample_length']
self.sample_direction = self.module.params['sample_direction']
self.counter_collector = self.module.params['counter_collector'] or list()
self.counter_interval = self.module.params['counter_interval']
self.state = self.module.params['state']
# state
self.config = "" # current config
self.sflow_dict = dict()
self.changed = False
self.updates_cmd = list()
self.commands = list()
self.results = dict()
self.proposed = dict()
self.existing = dict()
self.end_state = dict()
def __init_module__(self):
"""init module"""
required_together = [("collector_id", "collector_ip")]
self.module = AnsibleModule(
argument_spec=self.spec, required_together=required_together, supports_check_mode=True)
def check_response(self, con_obj, xml_name):
"""Check if response message is already succeed"""
xml_str = con_obj.xml
if "<ok/>" not in xml_str:
self.module.fail_json(msg='Error: %s failed.' % xml_name)
def netconf_set_config(self, xml_str, xml_name):
"""netconf set config"""
rcv_xml = set_nc_config(self.module, xml_str)
if "<ok/>" not in rcv_xml:
self.module.fail_json(msg='Error: %s failed.' % xml_name)
def get_sflow_dict(self):
""" sflow config dict"""
sflow_dict = dict(source=list(), agent=dict(), collector=list(),
sampling=dict(), counter=dict(), export=dict())
conf_str = CE_NC_GET_SFLOW % (
self.sflow_interface, self.sflow_interface)
if not self.collector_meth:
conf_str = conf_str.replace("<meth></meth>", "")
rcv_xml = get_nc_config(self.module, conf_str)
if "<data/>" in rcv_xml:
return sflow_dict
xml_str = rcv_xml.replace('\r', '').replace('\n', '').\
replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\
replace('xmlns="http://www.huawei.com/netconf/vrp"', "")
root = ElementTree.fromstring(xml_str)
# get source info
srcs = root.findall("sflow/sources/source")
if srcs:
for src in srcs:
attrs = dict()
for attr in src:
if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]:
attrs[attr.tag] = attr.text
sflow_dict["source"].append(attrs)
# get agent info
agent = root.find("sflow/agents/agent")
if agent:
for attr in agent:
if attr.tag in ["family", "ipv4Addr", "ipv6Addr"]:
sflow_dict["agent"][attr.tag] = attr.text
# get collector info
collectors = root.findall("sflow/collectors/collector")
if collectors:
for collector in collectors:
attrs = dict()
for attr in collector:
if attr.tag in ["collectorID", "family", "ipv4Addr", "ipv6Addr",
"vrfName", "datagramSize", "port", "description", "meth"]:
attrs[attr.tag] = attr.text
sflow_dict["collector"].append(attrs)
# get sampling info
sample = root.find("sflow/samplings/sampling")
if sample:
for attr in sample:
if attr.tag in ["ifName", "collectorID", "direction", "length", "rate"]:
sflow_dict["sampling"][attr.tag] = attr.text
# get counter info
counter = root.find("sflow/counters/counter")
if counter:
for attr in counter:
if attr.tag in ["ifName", "collectorID", "interval"]:
sflow_dict["counter"][attr.tag] = attr.text
# get export info
export = root.find("sflow/exports/export")
if export:
for attr in export:
if attr.tag == "ExportRoute":
sflow_dict["export"][attr.tag] = attr.text
return sflow_dict
def config_agent(self):
"""configures sFlow agent"""
xml_str = ''
if not self.agent_ip:
return xml_str
self.agent_version = get_ip_version(self.agent_ip)
if not self.agent_version:
self.module.fail_json(msg="Error: agent_ip is invalid.")
if self.state == "present":
if self.agent_ip != self.sflow_dict["agent"].get("ipv4Addr") \
and self.agent_ip != self.sflow_dict["agent"].get("ipv6Addr"):
xml_str += '<agents><agent operation="merge">'
xml_str += '<family>%s</family>' % self.agent_version
if self.agent_version == "ipv4":
xml_str += '<ipv4Addr>%s</ipv4Addr>' % self.agent_ip
self.updates_cmd.append("sflow agent ip %s" % self.agent_ip)
else:
xml_str += '<ipv6Addr>%s</ipv6Addr>' % self.agent_ip
self.updates_cmd.append("sflow agent ipv6 %s" % self.agent_ip)
xml_str += '</agent></agents>'
else:
if self.agent_ip == self.sflow_dict["agent"].get("ipv4Addr") \
or self.agent_ip == self.sflow_dict["agent"].get("ipv6Addr"):
xml_str += '<agents><agent operation="delete"></agent></agents>'
self.updates_cmd.append("undo sflow agent")
return xml_str
def config_source(self):
"""configures the source IP address for sFlow packets"""
xml_str = ''
if not self.source_ip:
return xml_str
self.source_version = get_ip_version(self.source_ip)
if not self.source_version:
self.module.fail_json(msg="Error: source_ip is invalid.")
src_dict = dict()
for src in self.sflow_dict["source"]:
if src.get("family") == self.source_version:
src_dict = src
break
if self.state == "present":
if self.source_ip != src_dict.get("ipv4Addr") \
and self.source_ip != src_dict.get("ipv6Addr"):
xml_str += '<sources><source operation="merge">'
xml_str += '<family>%s</family>' % self.source_version
if self.source_version == "ipv4":
xml_str += '<ipv4Addr>%s</ipv4Addr>' % self.source_ip
self.updates_cmd.append("sflow source ip %s" % self.source_ip)
else:
xml_str += '<ipv6Addr>%s</ipv6Addr>' % self.source_ip
self.updates_cmd.append(
"sflow source ipv6 %s" % self.source_ip)
xml_str += '</source ></sources>'
else:
if self.source_ip == src_dict.get("ipv4Addr"):
xml_str += '<sources><source operation="delete"><family>ipv4</family></source ></sources>'
self.updates_cmd.append("undo sflow source ip %s" % self.source_ip)
elif self.source_ip == src_dict.get("ipv6Addr"):
xml_str += '<sources><source operation="delete"><family>ipv6</family></source ></sources>'
self.updates_cmd.append("undo sflow source ipv6 %s" % self.source_ip)
return xml_str
def config_collector(self):
"""creates an sFlow collector and sets or modifies optional parameters for the sFlow collector"""
xml_str = ''
if not self.collector_id:
return xml_str
if self.state == "present" and not self.collector_ip:
return xml_str
if self.collector_ip:
self.collector_version = get_ip_version(self.collector_ip)
if not self.collector_version:
self.module.fail_json(msg="Error: collector_ip is invalid.")
# get collector dict
exist_dict = dict()
for collector in self.sflow_dict["collector"]:
if collector.get("collectorID") == self.collector_id:
exist_dict = collector
break
change = False
if self.state == "present":
if not exist_dict:
change = True
elif self.collector_version != exist_dict.get("family"):
change = True
elif self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"):
change = True
elif self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"):
change = True
elif self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"):
change = True
elif not self.collector_ip_vpn and exist_dict.get("vrfName") != "_public_":
change = True
elif self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"):
change = True
elif not self.collector_udp_port and exist_dict.get("port") != "6343":
change = True
elif self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"):
change = True
elif not self.collector_datagram_size and exist_dict.get("datagramSize") != "1400":
change = True
elif self.collector_meth and self.collector_meth != exist_dict.get("meth"):
change = True
elif not self.collector_meth and exist_dict.get("meth") and exist_dict.get("meth") != "meth":
change = True
elif self.collector_description and self.collector_description != exist_dict.get("description"):
change = True
elif not self.collector_description and exist_dict.get("description"):
change = True
else:
pass
else: # absent
# collector not exist
if not exist_dict:
return xml_str
if self.collector_version and self.collector_version != exist_dict.get("family"):
return xml_str
if self.collector_version == "ipv4" and self.collector_ip != exist_dict.get("ipv4Addr"):
return xml_str
if self.collector_version == "ipv6" and self.collector_ip != exist_dict.get("ipv6Addr"):
return xml_str
if self.collector_ip_vpn and self.collector_ip_vpn != exist_dict.get("vrfName"):
return xml_str
if self.collector_udp_port and self.collector_udp_port != exist_dict.get("port"):
return xml_str
if self.collector_datagram_size and self.collector_datagram_size != exist_dict.get("datagramSize"):
return xml_str
if self.collector_meth and self.collector_meth != exist_dict.get("meth"):
return xml_str
if self.collector_description and self.collector_description != exist_dict.get("description"):
return xml_str
change = True
if not change:
return xml_str
# update or delete
if self.state == "absent":
xml_str += '<collectors><collector operation="delete"><collectorID>%s</collectorID>' % self.collector_id
self.updates_cmd.append("undo collector %s" % self.collector_id)
else:
xml_str += '<collectors><collector operation="merge"><collectorID>%s</collectorID>' % self.collector_id
cmd = "sflow collector %s" % self.collector_id
xml_str += '<family>%s</family>' % self.collector_version
if self.collector_version == "ipv4":
cmd += " ip %s" % self.collector_ip
xml_str += '<ipv4Addr>%s</ipv4Addr>' % self.collector_ip
else:
cmd += " ipv6 %s" % self.collector_ip
xml_str += '<ipv6Addr>%s</ipv6Addr>' % self.collector_ip
if self.collector_ip_vpn:
cmd += " vpn-instance %s" % self.collector_ip_vpn
xml_str += '<vrfName>%s</vrfName>' % self.collector_ip_vpn
if self.collector_datagram_size:
cmd += " length %s" % self.collector_datagram_size
xml_str += '<datagramSize>%s</datagramSize>' % self.collector_datagram_size
if self.collector_udp_port:
cmd += " udp-port %s" % self.collector_udp_port
xml_str += '<port>%s</port>' % self.collector_udp_port
if self.collector_description:
cmd += " description %s" % self.collector_description
xml_str += '<description>%s</description>' % self.collector_description
else:
xml_str += '<description></description>'
if self.collector_meth:
if self.collector_meth == "enhanced":
cmd += " enhanced"
xml_str += '<meth>%s</meth>' % self.collector_meth
self.updates_cmd.append(cmd)
xml_str += "</collector></collectors>"
return xml_str
def config_sampling(self):
"""configure sflow sampling on an interface"""
xml_str = ''
if not self.sflow_interface:
return xml_str
if not self.sflow_dict["sampling"] and self.state == "absent":
return xml_str
self.updates_cmd.append("interface %s" % self.sflow_interface)
if self.state == "present":
xml_str += '<samplings><sampling operation="merge"><ifName>%s</ifName>' % self.sflow_interface
else:
xml_str += '<samplings><sampling operation="delete"><ifName>%s</ifName>' % self.sflow_interface
# sample_collector
if self.sample_collector:
if self.sflow_dict["sampling"].get("collectorID") \
and self.sflow_dict["sampling"].get("collectorID") != "invalid":
existing = self.sflow_dict["sampling"].get("collectorID").split(',')
else:
existing = list()
if self.state == "present":
diff = list(set(self.sample_collector) - set(existing))
if diff:
self.updates_cmd.append(
"sflow sampling collector %s" % ' '.join(diff))
new_set = list(self.sample_collector + existing)
xml_str += '<collectorID>%s</collectorID>' % ','.join(list(set(new_set)))
else:
same = list(set(self.sample_collector) & set(existing))
if same:
self.updates_cmd.append(
"undo sflow sampling collector %s" % ' '.join(same))
xml_str += '<collectorID>%s</collectorID>' % ','.join(list(set(same)))
# sample_rate
if self.sample_rate:
exist = bool(self.sample_rate == self.sflow_dict["sampling"].get("rate"))
if self.state == "present" and not exist:
self.updates_cmd.append(
"sflow sampling rate %s" % self.sample_rate)
xml_str += '<rate>%s</rate>' % self.sample_rate
elif self.state == "absent" and exist:
self.updates_cmd.append(
"undo sflow sampling rate %s" % self.sample_rate)
xml_str += '<rate>%s</rate>' % self.sample_rate
# sample_length
if self.sample_length:
exist = bool(self.sample_length == self.sflow_dict["sampling"].get("length"))
if self.state == "present" and not exist:
self.updates_cmd.append(
"sflow sampling length %s" % self.sample_length)
xml_str += '<length>%s</length>' % self.sample_length
elif self.state == "absent" and exist:
self.updates_cmd.append(
"undo sflow sampling length %s" % self.sample_length)
xml_str += '<length>%s</length>' % self.sample_length
# sample_direction
if self.sample_direction:
direction = list()
if self.sample_direction == "both":
direction = ["inbound", "outbound"]
else:
direction.append(self.sample_direction)
existing = list()
if self.sflow_dict["sampling"].get("direction"):
if self.sflow_dict["sampling"].get("direction") == "both":
existing = ["inbound", "outbound"]
else:
existing.append(
self.sflow_dict["sampling"].get("direction"))
if self.state == "present":
diff = list(set(direction) - set(existing))
if diff:
new_set = list(set(direction + existing))
self.updates_cmd.append(
"sflow sampling %s" % ' '.join(diff))
if len(new_set) > 1:
new_dir = "both"
else:
new_dir = new_set[0]
xml_str += '<direction>%s</direction>' % new_dir
else:
same = list(set(existing) & set(direction))
if same:
self.updates_cmd.append("undo sflow sampling %s" % ' '.join(same))
if len(same) > 1:
del_dir = "both"
else:
del_dir = same[0]
xml_str += '<direction>%s</direction>' % del_dir
if xml_str.endswith("</ifName>"):
self.updates_cmd.pop()
return ""
xml_str += '</sampling></samplings>'
return xml_str
def config_counter(self):
"""configures sflow counter on an interface"""
xml_str = ''
if not self.sflow_interface:
return xml_str
if not self.sflow_dict["counter"] and self.state == "absent":
return xml_str
self.updates_cmd.append("interface %s" % self.sflow_interface)
if self.state == "present":
xml_str += '<counters><counter operation="merge"><ifName>%s</ifName>' % self.sflow_interface
else:
xml_str += '<counters><counter operation="delete"><ifName>%s</ifName>' % self.sflow_interface
# counter_collector
if self.counter_collector:
if self.sflow_dict["counter"].get("collectorID") \
and self.sflow_dict["counter"].get("collectorID") != "invalid":
existing = self.sflow_dict["counter"].get("collectorID").split(',')
else:
existing = list()
if self.state == "present":
diff = list(set(self.counter_collector) - set(existing))
if diff:
self.updates_cmd.append("sflow counter collector %s" % ' '.join(diff))
new_set = list(self.counter_collector + existing)
xml_str += '<collectorID>%s</collectorID>' % ','.join(list(set(new_set)))
else:
same = list(set(self.counter_collector) & set(existing))
if same:
self.updates_cmd.append(
"undo sflow counter collector %s" % ' '.join(same))
xml_str += '<collectorID>%s</collectorID>' % ','.join(list(set(same)))
# counter_interval
if self.counter_interval:
exist = bool(self.counter_interval == self.sflow_dict["counter"].get("interval"))
if self.state == "present" and not exist:
self.updates_cmd.append(
"sflow counter interval %s" % self.counter_interval)
xml_str += '<interval>%s</interval>' % self.counter_interval
elif self.state == "absent" and exist:
self.updates_cmd.append(
"undo sflow counter interval %s" % self.counter_interval)
xml_str += '<interval>%s</interval>' % self.counter_interval
if xml_str.endswith("</ifName>"):
self.updates_cmd.pop()
return ""
xml_str += '</counter></counters>'
return xml_str
def config_export(self):
"""configure sflow export"""
xml_str = ''
if not self.export_route:
return xml_str
if self.export_route == "enable":
if self.sflow_dict["export"] and self.sflow_dict["export"].get("ExportRoute") == "disable":
xml_str = '<exports><export operation="delete"><ExportRoute>disable</ExportRoute></export></exports>'
self.updates_cmd.append("undo sflow export extended-route-data disable")
else: # disable
if not self.sflow_dict["export"] or self.sflow_dict["export"].get("ExportRoute") != "disable":
xml_str = '<exports><export operation="create"><ExportRoute>disable</ExportRoute></export></exports>'
self.updates_cmd.append("sflow export extended-route-data disable")
return xml_str
def netconf_load_config(self, xml_str):
"""load sflow config by netconf"""
if not xml_str:
return
xml_cfg = """
<config>
<sflow xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
%s
</sflow>
</config>""" % xml_str
self.netconf_set_config(xml_cfg, "SET_SFLOW")
self.changed = True
def check_params(self):
"""Check all input params"""
# check agent_ip
if self.agent_ip:
self.agent_ip = self.agent_ip.upper()
if not check_ip_addr(self.agent_ip):
self.module.fail_json(msg="Error: agent_ip is invalid.")
# check source_ip
if self.source_ip:
self.source_ip = self.source_ip.upper()
if not check_ip_addr(self.source_ip):
self.module.fail_json(msg="Error: source_ip is invalid.")
# check collector
if self.collector_id:
# check collector_ip and collector_ip_vpn
if self.collector_ip:
self.collector_ip = self.collector_ip.upper()
if not check_ip_addr(self.collector_ip):
self.module.fail_json(
msg="Error: collector_ip is invalid.")
if self.collector_ip_vpn and not is_valid_ip_vpn(self.collector_ip_vpn):
self.module.fail_json(
msg="Error: collector_ip_vpn is invalid.")
# check collector_datagram_size ranges from 1024 to 8100
if self.collector_datagram_size:
if not self.collector_datagram_size.isdigit():
self.module.fail_json(
msg="Error: collector_datagram_size is not digit.")
if int(self.collector_datagram_size) < 1024 or int(self.collector_datagram_size) > 8100:
self.module.fail_json(
msg="Error: collector_datagram_size is not ranges from 1024 to 8100.")
# check collector_udp_port ranges from 1 to 65535
if self.collector_udp_port:
if not self.collector_udp_port.isdigit():
self.module.fail_json(
msg="Error: collector_udp_port is not digit.")
if int(self.collector_udp_port) < 1 or int(self.collector_udp_port) > 65535:
self.module.fail_json(
msg="Error: collector_udp_port is not ranges from 1 to 65535.")
# check collector_description 1 to 255 case-sensitive characters
if self.collector_description:
if self.collector_description.count(" "):
self.module.fail_json(
msg="Error: collector_description should without spaces.")
if len(self.collector_description) < 1 or len(self.collector_description) > 255:
self.module.fail_json(
msg="Error: collector_description is not ranges from 1 to 255.")
# check sflow_interface
if self.sflow_interface:
intf_type = get_interface_type(self.sflow_interface)
if not intf_type:
self.module.fail_json(msg="Error: intf_type is invalid.")
if intf_type not in ['ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'eth-trunk']:
self.module.fail_json(
msg="Error: interface %s is not support sFlow." % self.sflow_interface)
# check sample_collector
if self.sample_collector:
self.sample_collector.sort()
if self.sample_collector not in [["1"], ["2"], ["1", "2"]]:
self.module.fail_json(
msg="Error: sample_collector is invalid.")
# check sample_rate ranges from 1 to 4294967295
if self.sample_rate:
if not self.sample_rate.isdigit():
self.module.fail_json(
msg="Error: sample_rate is not digit.")
if int(self.sample_rate) < 1 or int(self.sample_rate) > 4294967295:
self.module.fail_json(
msg="Error: sample_rate is not ranges from 1 to 4294967295.")
# check sample_length ranges from 18 to 512
if self.sample_length:
if not self.sample_length.isdigit():
self.module.fail_json(
msg="Error: sample_rate is not digit.")
if int(self.sample_length) < 18 or int(self.sample_length) > 512:
self.module.fail_json(
msg="Error: sample_length is not ranges from 18 to 512.")
# check counter_collector
if self.counter_collector:
self.counter_collector.sort()
if self.counter_collector not in [["1"], ["2"], ["1", "2"]]:
self.module.fail_json(
msg="Error: counter_collector is invalid.")
# counter_interval ranges from 10 to 4294967295
if self.counter_interval:
if not self.counter_interval.isdigit():
self.module.fail_json(
msg="Error: counter_interval is not digit.")
if int(self.counter_interval) < 10 or int(self.counter_interval) > 4294967295:
self.module.fail_json(
msg="Error: sample_length is not ranges from 10 to 4294967295.")
if self.rate_limit or self.rate_limit_slot or self.forward_enp_slot:
self.module.fail_json(msg="Error: The following parameters cannot be configured"
"because XML mode is not supported:rate_limit,rate_limit_slot,forward_enp_slot.")
def get_proposed(self):
"""get proposed info"""
# base config
if self.agent_ip:
self.proposed["agent_ip"] = self.agent_ip
if self.source_ip:
self.proposed["source_ip"] = self.source_ip
if self.export_route:
self.proposed["export_route"] = self.export_route
if self.rate_limit:
self.proposed["rate_limit"] = self.rate_limit
self.proposed["rate_limit_slot"] = self.rate_limit_slot
if self.forward_enp_slot:
self.proposed["forward_enp_slot"] = self.forward_enp_slot
if self.collector_id:
self.proposed["collector_id"] = self.collector_id
if self.collector_ip:
self.proposed["collector_ip"] = self.collector_ip
self.proposed["collector_ip_vpn"] = self.collector_ip_vpn
if self.collector_datagram_size:
self.proposed[
"collector_datagram_size"] = self.collector_datagram_size
if self.collector_udp_port:
self.proposed["collector_udp_port"] = self.collector_udp_port
if self.collector_meth:
self.proposed["collector_meth"] = self.collector_meth
if self.collector_description:
self.proposed[
"collector_description"] = self.collector_description
# sample and counter config
if self.sflow_interface:
self.proposed["sflow_interface"] = self.sflow_interface
if self.sample_collector:
self.proposed["sample_collector"] = self.sample_collector
if self.sample_rate:
self.proposed["sample_rate"] = self.sample_rate
if self.sample_length:
self.proposed["sample_length"] = self.sample_length
if self.sample_direction:
self.proposed["sample_direction"] = self.sample_direction
if self.counter_collector:
self.proposed["counter_collector"] = self.counter_collector
if self.counter_interval:
self.proposed["counter_interval"] = self.counter_interval
self.proposed["state"] = self.state
def get_existing(self):
"""get existing info"""
if not self.sflow_dict:
return
if self.agent_ip:
self.existing["agent"] = self.sflow_dict["agent"]
if self.source_ip:
self.existing["source"] = self.sflow_dict["source"]
if self.collector_id:
self.existing["collector"] = self.sflow_dict["collector"]
if self.export_route:
self.existing["export"] = self.sflow_dict["export"]
if self.sflow_interface:
self.existing["sampling"] = self.sflow_dict["sampling"]
self.existing["counter"] = self.sflow_dict["counter"]
def get_end_state(self):
"""get end state info"""
sflow_dict = self.get_sflow_dict()
if not sflow_dict:
return
if self.agent_ip:
self.end_state["agent"] = sflow_dict["agent"]
if self.source_ip:
self.end_state["source"] = sflow_dict["source"]
if self.collector_id:
self.end_state["collector"] = sflow_dict["collector"]
if self.export_route:
self.end_state["export"] = sflow_dict["export"]
if self.sflow_interface:
self.end_state["sampling"] = sflow_dict["sampling"]
self.end_state["counter"] = sflow_dict["counter"]
if self.existing == self.end_state:
self.changed = False
def work(self):
"""worker"""
self.check_params()
self.sflow_dict = self.get_sflow_dict()
self.get_existing()
self.get_proposed()
# deal present or absent
xml_str = ''
if self.export_route:
xml_str += self.config_export()
if self.agent_ip:
xml_str += self.config_agent()
if self.source_ip:
xml_str += self.config_source()
if self.state == "present":
if self.collector_id and self.collector_ip:
xml_str += self.config_collector()
if self.sflow_interface:
xml_str += self.config_sampling()
xml_str += self.config_counter()
else:
if self.sflow_interface:
xml_str += self.config_sampling()
xml_str += self.config_counter()
if self.collector_id:
xml_str += self.config_collector()
if xml_str:
self.netconf_load_config(xml_str)
self.changed = True
self.get_end_state()
self.results['changed'] = self.changed
self.results['proposed'] = self.proposed
self.results['existing'] = self.existing
self.results['end_state'] = self.end_state
if self.changed:
self.results['updates'] = self.updates_cmd
else:
self.results['updates'] = list()
self.module.exit_json(**self.results)
def main():
"""Module main"""
argument_spec = dict(
agent_ip=dict(required=False, type='str'),
source_ip=dict(required=False, type='str'),
export_route=dict(required=False, type='str',
choices=['enable', 'disable']),
rate_limit=dict(required=False, removed_in_version=2.13, type='str'),
rate_limit_slot=dict(required=False, removed_in_version=2.13, type='str'),
forward_enp_slot=dict(required=False, removed_in_version=2.13, type='str'),
collector_id=dict(required=False, type='str', choices=['1', '2']),
collector_ip=dict(required=False, type='str'),
collector_ip_vpn=dict(required=False, type='str'),
collector_datagram_size=dict(required=False, type='str'),
collector_udp_port=dict(required=False, type='str'),
collector_meth=dict(required=False, type='str',
choices=['meth', 'enhanced']),
collector_description=dict(required=False, type='str'),
sflow_interface=dict(required=False, type='str'),
sample_collector=dict(required=False, type='list'),
sample_rate=dict(required=False, type='str'),
sample_length=dict(required=False, type='str'),
sample_direction=dict(required=False, type='str',
choices=['inbound', 'outbound', 'both']),
counter_collector=dict(required=False, type='list'),
counter_interval=dict(required=False, type='str'),
state=dict(required=False, default='present',
choices=['present', 'absent'])
)
argument_spec.update(ce_argument_spec)
module = Sflow(argument_spec)
module.work()
if __name__ == '__main__':
main()