diff --git a/lib/ansible/modules/network/cloudengine/ce_sflow.py b/lib/ansible/modules/network/cloudengine/ce_sflow.py
new file mode 100644
index 00000000000..d018c4a0dae
--- /dev/null
+++ b/lib/ansible/modules/network/cloudengine/ce_sflow.py
@@ -0,0 +1,1347 @@
+#!/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 .
+#
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = """
+---
+module: ce_sflow
+version_added: "2.4"
+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 (@CloudEngine-Ansible)
+options:
+ agent_ip:
+ description:
+ - Specifies the IPv4/IPv6 address of an sFlow agent.
+ required: false
+ default: null
+ source_ip:
+ description:
+ - Specifies the source IPv4/IPv6 address of sFlow packets.
+ required: false
+ default: null
+ collector_id:
+ description:
+ - Specifies the ID of an sFlow collector. This ID is used when you specify
+ the collector in subsequent sFlow configuration.
+ required: false
+ default: null
+ choices: ['1', '2']
+ collector_ip:
+ description:
+ - Specifies the IPv4/IPv6 address of the sFlow collector.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ sflow_interface:
+ description:
+ - Full name of interface for Flow Sampling or Counter.
+ It must be a physical interface, Eth-Trunk, or Layer 2 subinterface.
+ required: false
+ default: null
+ sample_collector:
+ description:
+ - Indicates the ID list of the collector.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ sample_direction:
+ description:
+ - Enables flow sampling in the inbound or outbound direction.
+ required: false
+ default: null
+ choices: ['inbound', 'outbound', 'both']
+ counter_collector:
+ description:
+ - Indicates the ID list of the counter collector.
+ required: false
+ default: null
+ counter_interval:
+ description:
+ - Indicates the the counter sampling interval.
+ The value is an integer that ranges from 10 to 4294967295, in seconds. The default value is 20.
+ required: false
+ default: null
+ export_route:
+ description:
+ - Configures the sFlow packets sent by the switch not to carry routing information.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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.
+ required: false
+ default: null
+ 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'.
+ required: false
+ default: null
+ state:
+ description:
+ - Determines whether the config should be present or not
+ on the device.
+ required: false
+ 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: boolean
+ sample: true
+'''
+
+import sys
+import re
+import socket
+from xml.etree import ElementTree
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ce import get_nc_config, set_nc_config, ce_argument_spec
+from ansible.module_utils.ce import get_config, load_config
+
+CE_NC_GET_SFLOW = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %s
+
+
+
+
+
+
+
+
+ %s
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+
+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 check_ip_addr(ipaddr):
+ """check ip address, Supports IPv4 and IPv6"""
+
+ if not ipaddr or '\x00' in ipaddr:
+ return False
+
+ try:
+ res = socket.getaddrinfo(ipaddr, 0, socket.AF_UNSPEC,
+ socket.SOCK_STREAM,
+ 0, socket.AI_NUMERICHOST)
+ return bool(res)
+ except socket.gaierror:
+ err = sys.exc_info()[1]
+ if err.args[0] == socket.EAI_NONAME:
+ return False
+ raise
+
+ 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()
+
+
+def get_rate_limit(config):
+ """get sflow management-plane export rate-limit info"""
+
+ get = re.findall(r"sflow management-plane export rate-limit ([0-9]+) slot ([0-9]+)", config)
+ if not get:
+ get = re.findall(r"sflow management-plane export rate-limit ([0-9]+)", config)
+ if not get:
+ return None
+ else:
+ return dict(rate_limit=get[0])
+ else:
+ limit = list()
+ for slot in get:
+ limit.append(dict(rate_limit=slot[0], slot_id=slot[1]))
+ return limit
+
+
+def get_forward_enp(config):
+ """get assign forward enp sflow enable slot info"""
+
+ get = re.findall(r"assign forward enp sflow enable slot (\S+)", config)
+ if not get:
+ return None
+ else:
+ return list(get)
+
+
+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 "" 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 "" not in rcv_xml:
+ self.module.fail_json(msg='Error: %s failed.' % xml_name)
+
+ def cli_load_config(self, commands):
+ """load config by cli"""
+
+ if not self.module.check_mode:
+ load_config(self.module, commands)
+
+ def get_current_config(self):
+ """get current configuration"""
+
+ flags = list()
+ exp = ""
+ if self.rate_limit:
+ exp += "assign sflow management-plane export rate-limit %s" % self.rate_limit
+ if self.rate_limit_slot:
+ exp += " slot %s" % self.rate_limit_slot
+ exp += "$"
+
+ if self.forward_enp_slot:
+ if exp:
+ exp += "|"
+ exp += "assign forward enp sflow enable slot %s$" % self.forward_enp_slot
+
+ if exp:
+ exp = " | ignore-case include " + exp
+ flags.append(exp)
+ return get_config(self.module, flags)
+ else:
+ return ""
+
+ def cli_add_command(self, command, undo=False):
+ """add command to self.update_cmd and self.commands"""
+
+ if undo and command.lower() not in ["quit", "return"]:
+ cmd = "undo " + command
+ else:
+ cmd = command
+
+ self.commands.append(cmd) # set to device
+ if command.lower() not in ["quit", "return"]:
+ self.updates_cmd.append(cmd) # show updates result
+
+ 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("", "")
+
+ rcv_xml = get_nc_config(self.module, conf_str)
+
+ if "" 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("data/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("data/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("data/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("data/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("data/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("data/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 += ''
+ xml_str += '%s' % self.agent_version
+ if self.agent_version == "ipv4":
+ xml_str += '%s' % self.agent_ip
+ self.updates_cmd.append("sflow agent ip %s" % self.agent_ip)
+ else:
+ xml_str += '%s' % self.agent_ip
+ self.updates_cmd.append("sflow agent ipv6 %s" % self.agent_ip)
+ xml_str += ''
+
+ else:
+ if self.agent_ip == self.sflow_dict["agent"].get("ipv4Addr") \
+ or self.agent_ip == self.sflow_dict["agent"].get("ipv6Addr"):
+ xml_str += ''
+ 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 += ''
+ else:
+ if self.source_ip == src_dict.get("ipv4Addr"):
+ xml_str += ''
+ self.updates_cmd.append("undo sflow source ip %s" % self.source_ip)
+ elif self.source_ip == src_dict.get("ipv6Addr"):
+ xml_str += ''
+ 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 += '%s' % self.collector_id
+ self.updates_cmd.append("undo collector %s" % self.collector_id)
+ else:
+ xml_str += '%s' % self.collector_id
+ cmd = "sflow collector %s" % self.collector_id
+ xml_str += '%s' % self.collector_version
+ if self.collector_version == "ipv4":
+ cmd += " ip %s" % self.collector_ip
+ xml_str += '%s' % self.collector_ip
+ else:
+ cmd += " ipv6 %s" % self.collector_ip
+ xml_str += '%s' % self.collector_ip
+ if self.collector_ip_vpn:
+ cmd += " vpn-instance %s" % self.collector_ip_vpn
+ xml_str += '%s' % self.collector_ip_vpn
+ if self.collector_datagram_size:
+ cmd += " length %s" % self.collector_datagram_size
+ xml_str += '%s' % self.collector_datagram_size
+ if self.collector_udp_port:
+ cmd += " udp-port %s" % self.collector_udp_port
+ xml_str += '%s' % self.collector_udp_port
+ if self.collector_description:
+ cmd += " description %s" % self.collector_description
+ xml_str += '%s' % self.collector_description
+ else:
+ xml_str += ''
+ if self.collector_meth:
+ if self.collector_meth == "enhanced":
+ cmd += " enhanced"
+ xml_str += '%s' % self.collector_meth
+ self.updates_cmd.append(cmd)
+
+ xml_str += ""
+
+ 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 += '%s' % self.sflow_interface
+ else:
+ xml_str += '%s' % 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 += '%s' % ','.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 += '%s' % ','.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 += '%s' % self.sample_rate
+ elif self.state == "absent" and exist:
+ self.updates_cmd.append(
+ "undo sflow sampling rate %s" % self.sample_rate)
+ xml_str += '%s' % 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 += '%s' % self.sample_length
+ elif self.state == "absent" and exist:
+ self.updates_cmd.append(
+ "undo sflow sampling length %s" % self.sample_length)
+ xml_str += '%s' % 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 += '%s' % 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 += '%s' % del_dir
+
+ if xml_str.endswith(""):
+ self.updates_cmd.pop()
+ return ""
+
+ xml_str += ''
+
+ 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 += '%s' % self.sflow_interface
+ else:
+ xml_str += '%s' % 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 += '%s' % ','.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 += '%s' % ','.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 += '%s' % self.counter_interval
+ elif self.state == "absent" and exist:
+ self.updates_cmd.append(
+ "undo sflow counter interval %s" % self.counter_interval)
+ xml_str += '%s' % self.counter_interval
+
+ if xml_str.endswith(""):
+ self.updates_cmd.pop()
+ return ""
+
+ xml_str += ''
+
+ 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 = 'disable'
+ 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 = 'disable'
+ self.updates_cmd.append("sflow export extended-route-data disable")
+
+ return xml_str
+
+ def config_assign(self):
+ """configure assign"""
+
+ # assign sflow management-plane export rate-limit rate-limit [ slot slot-id ]
+ if self.rate_limit:
+ cmd = "assign sflow management-plane export rate-limit %s" % self.rate_limit
+ if self.rate_limit_slot:
+ cmd += " slot %s" % self.rate_limit_slot
+ exist = is_config_exist(self.config, cmd)
+ if self.state == "present" and not exist:
+ self.cli_add_command(cmd)
+ elif self.state == "absent" and exist:
+ self.cli_add_command(cmd, undo=True)
+
+ # assign forward enp sflow enable slot { slot-id | all }
+ if self.forward_enp_slot:
+ cmd = "assign forward enp sflow enable slot %s" % self.forward_enp_slot
+ exist = is_config_exist(self.config, cmd)
+ if self.state == "present" and not exist:
+ self.cli_add_command(cmd)
+ elif self.state == "absent" and exist:
+ self.cli_add_command(cmd, undo=True)
+
+ def netconf_load_config(self, xml_str):
+ """load sflow config by netconf"""
+
+ if not xml_str:
+ return
+
+ xml_cfg = """
+
+
+ %s
+
+ """ % 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.")
+
+ # check rate_limit ranges from 100 to 1500 and check rate_limit_slot
+ if self.rate_limit:
+ if not self.rate_limit.isdigit():
+ self.module.fail_json(msg="Error: rate_limit is not digit.")
+ if int(self.rate_limit) < 100 or int(self.rate_limit) > 1500:
+ self.module.fail_json(
+ msg="Error: rate_limit is not ranges from 100 to 1500.")
+ if self.rate_limit_slot and not self.rate_limit_slot.isdigit():
+ self.module.fail_json(
+ msg="Error: rate_limit_slot is not digit.")
+
+ # check forward_enp_slot
+ if self.forward_enp_slot:
+ self.forward_enp_slot.lower()
+ if not self.forward_enp_slot.isdigit() and self.forward_enp_slot != "all":
+ self.module.fail_json(
+ msg="Error: forward_enp_slot is invalid.")
+
+ 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 self.config:
+ if self.rate_limit:
+ self.existing["rate_limit"] = get_rate_limit(self.config)
+ if self.forward_enp_slot:
+ self.existing["forward_enp_slot"] = get_forward_enp(
+ self.config)
+
+ 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"""
+
+ config = self.get_current_config()
+ if config:
+ if self.rate_limit:
+ self.end_state["rate_limit"] = get_rate_limit(config)
+ if self.forward_enp_slot:
+ self.end_state["forward_enp_slot"] = get_forward_enp(config)
+
+ 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"]
+
+ def work(self):
+ """worker"""
+
+ self.check_params()
+ self.sflow_dict = self.get_sflow_dict()
+ self.config = self.get_current_config()
+ 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 self.rate_limit or self.forward_enp_slot:
+ self.config_assign()
+
+ if self.commands:
+ self.cli_load_config(self.commands)
+ self.changed = True
+
+ 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, type='str'),
+ rate_limit_slot=dict(required=False, type='str'),
+ forward_enp_slot=dict(required=False, 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()