From a734e1ce16f583f19bf00e538260bf0488f1282f Mon Sep 17 00:00:00 2001 From: Lior Avramov <73036155+liorghub@users.noreply.github.com> Date: Sun, 4 Dec 2022 17:14:25 +0200 Subject: [PATCH] Add ECMP calculator tool (#12482) - Why I did it Added ECMP calculator tool. - How I did it New files were added. - How to verify it Manual tests performed according to tests chapter in HLD Automated tests will be added by verification. --- .../mellanox/docker-syncd-mlnx/Dockerfile.j2 | 7 + .../ecmp_calculator/README.md | 136 +++++ .../ecmp_calculator/ecmp_calc.py | 510 ++++++++++++++++++ .../ecmp_calculator/ecmp_calc_sdk.py | 456 ++++++++++++++++ .../ecmp_calculator/packet_scheme.py | 140 +++++ .../docker-syncd-mlnx/lib/port_utils.py | 122 +++++ 6 files changed, 1371 insertions(+) create mode 100644 platform/mellanox/docker-syncd-mlnx/ecmp_calculator/README.md create mode 100755 platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc.py create mode 100755 platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc_sdk.py create mode 100755 platform/mellanox/docker-syncd-mlnx/ecmp_calculator/packet_scheme.py create mode 100755 platform/mellanox/docker-syncd-mlnx/lib/port_utils.py diff --git a/platform/mellanox/docker-syncd-mlnx/Dockerfile.j2 b/platform/mellanox/docker-syncd-mlnx/Dockerfile.j2 index cd3f74bcfaf3..fd50f67495a9 100755 --- a/platform/mellanox/docker-syncd-mlnx/Dockerfile.j2 +++ b/platform/mellanox/docker-syncd-mlnx/Dockerfile.j2 @@ -32,6 +32,7 @@ RUN apt-get update && \ python3-pip \ python3-dev \ python-is-python3 \ + python3-jsonschema \ {%- if ENABLE_ASAN == "y" %} libasan6 \ {%- endif %} @@ -70,4 +71,10 @@ RUN mkdir -p /etc/supervisor/conf.d/ RUN sonic-cfggen -a "{\"ENABLE_ASAN\":\"{{ENABLE_ASAN}}\"}" -t /usr/share/sonic/templates/supervisord.conf.j2 > /etc/supervisor/conf.d/supervisord.conf RUN rm -f /usr/share/sonic/templates/supervisord.conf.j2 +RUN mkdir -p /usr/lib/ecmp_calc +COPY ["ecmp_calculator/ecmp_calc.py", "/usr/bin"] +COPY ["ecmp_calculator/ecmp_calc_sdk.py", "/usr/lib/ecmp_calc"] +COPY ["ecmp_calculator/packet_scheme.py", "/usr/lib/ecmp_calc"] +COPY ["lib/port_utils.py", "/usr/lib"] + ENTRYPOINT ["/usr/local/bin/supervisord"] diff --git a/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/README.md b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/README.md new file mode 100644 index 000000000000..7eb90ec9063b --- /dev/null +++ b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/README.md @@ -0,0 +1,136 @@ +# SONiC ECMP Calculator + +## Description +An equal cost multipath (ECMP) is formed when routing table contains multiple next hop addresses for the same destination IP address. +ECMP will load balance the outbound traffic between the IP interfaces. +The purpose of ECMP calculator is to calculate which IP interface ECMP will choose and return the physical interface packet will egress from. +Packet is defined by a JSON file given as an argument to the tool. + +## Usage notes +1. ECMP calculator performs its calculations based on the current operational state of the router. In order to calculate the egress port, it fetches routes from HW. Routes exist in HW only for next hops with a resolved ARP. +2. ECMP calculator supports only routed packets. +- IPv4/IPv6 TCP/UDP packets +- IPinIP and VXLAN encapsulated packets +3. Changes done in the packet classification (e.g. ACL, PBR) are not taken into consideration during calculation. + +## Command line interface +1. User shall provide the following input parameters: +- JSON file describing a packet +- Ingress port (e.g. "Ethernet0", must pe a physical interface) +- Debug option for debug purposes (optional) +- VRF name (optional) +2. Usage example: +``` +$ show ip ecmp-egress-port --packet /tmp/packet.json --ingress-port Ethernet0 --vrf Vrf_red --debug +Egress port: Ethernet4 +``` +ECMP calculator is a vendor specific tool. If tool was not implemented and CLI command is being called, the following message will be returned to user: +``` +$ show ip ecmp-egress-port --packet /tmp/packet.json --ingress-port Ethernet0 --vrf Vrf_red --debug +ECMP calculator is not available in this image +``` + +## Packet JSON +1. Numbers in packet JSON must be in base-ten. +2. For packets with single header, outer header shall be provided. +3. The following table defines the structure of a packet JSON file. + +| ecmp_hash | | | | | | | +|-----------|-------------|-------|-------------|-------------|--------|----------------------------------------------------------------------------------| +| | packet_info | | | | object | | +| | | outer | | | object | | +| | | | layer2 | | object | | +| | | | | smac | string | | +| | | | | dmac | string | | +| | | | | ethertype | number | 16bits, needed for IPv4 or IPv6 packet | +| | | | | outer_vid | number | 12bits | +| | | | | outer_pcp | number | 3bits | +| | | | | outer_dei | number | 1bits | +| | | | | inner_vid | number | QinQ | +| | | | | inner_pcp | number | QinQ | +| | | | | inner_dei | number | QinQ | +| | | | ipv4 | | object | | +| | | | | sip | string | | +| | | | | dip | string | | +| | | | | proto | number | 8bits | +| | | | | dscp | number | 6bits | +| | | | | ecn | number | 2bits | +| | | | | mflag | number | 1bit | +| | | | | l3_length | number | 16bits | +| | | | ipv6 | | object | should not co-exist with ipv4 field | +| | | | | sip | string | | +| | | | | dip | string | | +| | | | | mflag | number | 1bit | +| | | | | next_header | number | 8bits | +| | | | | dscp | number | 6bits | +| | | | | ecn | number | 2bits | +| | | | | l3_length | number | 16bits | +| | | | | flow_label | number | 20bits | +| | | | tcp_udp | | object | | +| | | | | sport | number | 16bits | +| | | | | dport | number | 16bits | +| | | | vxlan_nvgre | | object | | +| | | | | vni | number | 24bits | +| | | inner | | | object | overlay | +| | | | layer2 | | object | | +| | | | | smac | string | | +| | | | | dmac | string | | +| | | | | ethertype | number | 16bits | +| | | | ipv4 | | object | | +| | | | | sip | string | | +| | | | | dip | string | | +| | | | | mflag | number | 1bit | +| | | | | proto | number | 8bits | +| | | | ipv6 | | object | should not co-exist with ipv4 field | +| | | | | sip | string | | +| | | | | dip | string | | +| | | | | mflag | number | 1bit | +| | | | | next_header | number | 8bits | +| | | | | flow_label | number | 20bits | +| | | | tcp_udp | | object | | +| | | | | sport | number | 16bits | +| | | | | dport | number | 16bits | + +4. Packet JSON file example + +```json +{ + "packet_info": { + "outer": { + "ipv4": { + "sip": "10.10.10.10", + "dip": "3.3.3.3", + "proto": 17 + }, + "layer2": { + "smac": "24:8a:07:1e:82:ed", + "dmac": "1c:34:da:1c:a1:00", + "ethertype": 2048 + }, + "tcp_udp": { + "sport": 100, + "dport": 4789 + }, + "vxlan_nvgre": { + "vni": 100 + } + }, + "inner": { + "layer2": { + "smac": "11:11:11:11:11:11", + "dmac": "22:22:22:22:22:22", + "ethertype": 2048 + }, + "ipv4": { + "sip": "1.1.1.1", + "dip": "2.2.2.3", + "proto": 17, + "mflag": 0 + }, + "tcp_udp": { + "sport": 100, + "dport": 200 + } + } + } +} \ No newline at end of file diff --git a/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc.py b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc.py new file mode 100755 index 000000000000..286a40fd2027 --- /dev/null +++ b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python3 + +import json, jsonschema +import argparse +import ipaddress +import re +import subprocess +import pprint +import os +import sys + +usr_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +lib_path = os.path.join(usr_path, "lib") +ecmp_lib_path = os.path.join(lib_path, "ecmp_calc") +sys.path.append(lib_path) +sys.path.append(ecmp_lib_path) + +from ecmp_calc_sdk import sx_open_sdk_connection, sx_get_active_vrids, sx_router_get_ecmp_id, \ + sx_router_ecmp_nexthops_get, sx_get_router_interface, \ + sx_port_vport_base_get, sx_router_neigh_get_mac, sx_fdb_uc_mac_addr_get, \ + sx_lag_port_group_get, sx_make_ip_prefix_v4, sx_make_ip_prefix_v6, \ + sx_vlan_ports_get, sx_ip_addr_to_str, sx_close_sdk_connection, \ + PORT, VPORT, VLAN, SX_ENTRY_NOT_FOUND +from packet_scheme import PACKET_SCHEME +from port_utils import sx_get_ports_map, is_lag + +IP_VERSION_IPV4 = 1 +IP_VERSION_IPV6 = 2 +PORT_CHANNEL_IDX = 1 +VRF_NAME_IDX = 1 +IP_VERSION_MAX_MASK_LEN = {IP_VERSION_IPV4: 32, IP_VERSION_IPV6: 128} + +INTF_TABLE = 'INTF_TABLE' +HASH_CALC_PATH = '/usr/bin/sx_hash_calculator' +HASH_CALC_INPUT_FILE = "/tmp/hash_calculator_input.json" +HASH_CALC_OUTPUT_FILE = "/tmp/hash_calculator_output.json" + +def exec_cmd(cmd): + """ Execute shell command """ + return subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False).decode("utf-8") + +def is_mac_valid(mac): + return bool(re.match("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", mac)) + +def is_ip_valid(address, ip_version): + try: + if ip_version == IP_VERSION_IPV4: + ip = ipaddress.IPv4Address(address) + invalid_list = ['0.0.0.0','255.255.255.255'] + else: + ip = ipaddress.IPv6Address(address) + invalid_list = ['0::0'] + + if ip.is_link_local: + print ("Link local IP {} is not valid".format(ip)) + return False + + if ip in invalid_list: + print ("IP {} is not valid".format(ip)) + return False + + if ip.is_multicast: + print ("Multicast IP {} is not valid".format(ip)) + return False + + if ip.is_loopback: + print ("Loopback IP {} is not valid".format(ip)) + return False + + except ipaddress.AddressValueError: + return False + + return True + +def load_json(filename): + data = None + with open(filename) as f: + try: + data = json.load(f) + except json.JSONDecodeError as e: + raise ValueError("Failed to load JSON file '{}', error: '{}'".format(filename, e)) + return data + +def create_network_addr(ip_addr, mask_len, ip_version): + ip_addr_mask = "{}/{}".format(ip_addr, mask_len) + + if ip_version == IP_VERSION_IPV4: + network_addr = ipaddress.IPv4Network(ip_addr_mask,strict = False) + else: + network_addr = ipaddress.IPv6Network(ip_addr_mask,strict = False) + + network_addr_ip = network_addr.with_netmask.split('/')[0] + network_addr_mask = network_addr.with_netmask.split('/')[1] + + if ip_version == IP_VERSION_IPV4: + network_addr = sx_make_ip_prefix_v4(network_addr_ip, network_addr_mask) + else: + network_addr = sx_make_ip_prefix_v6(network_addr_ip, network_addr_mask) + + return network_addr + +class EcmpCalcExit(Exception): + pass + +class EcmpCalc: + def __init__(self): + self.packet = {} + self.ports_map = {} + self.ecmp_ids = {} + self.next_hops = {} + self.user_vrf = '' + self.ingress_port = "" + self.egress_ports = [] + self.debug = False + + self.open_sdk_connection() + self.init_ports_map() + self.get_active_vrids() + + def __del__(self): + self.close_sdk_connection() + self.cleanup() + + def cleanup(self): + for filename in [HASH_CALC_INPUT_FILE, HASH_CALC_OUTPUT_FILE]: + if os.path.exists(filename): + os.remove(filename) + + def close_sdk_connection(self): + sx_close_sdk_connection(self.handle) + + def open_sdk_connection(self): + self.handle = sx_open_sdk_connection() + + def debug_print(self, *args, **kwargs): + if self.debug == True: + print(*args, **kwargs) + + def init_ports_map(self): + self.ports_map = sx_get_ports_map(self.handle) + + def validate_ingress_port(self, interface): + if interface not in self.ports_map.values(): + raise ValueError("Invalid interface {}".format(interface)) + self.ingress_port = interface + + def validate_args(self, interface, packet, vrf, debug): + if (debug is True): + self.debug = True + + self.validate_ingress_port(interface) + self.validate_packet_json(packet) + + if (vrf is not None): + self.user_vrf = vrf + if not self.validate_vrf(): + raise ValueError("VRF validation failed: VRF {} does not exist".format(self.user_vrf)) + + def validate_vrf(self): + query_output = exec_cmd(['/usr/bin/redis-cli', '-n', '0', 'keys','*VRF*']).strip() + if not query_output: + return False + + vrf_entries= query_output.split('\n') + for entry in vrf_entries: + vrf = entry.split(':')[VRF_NAME_IDX] + if vrf == self.user_vrf: + return True + + return False + + def get_ecmp_id(self): + ip_addr = self.dst_ip + ip_version = self.ip_version + max_mask_len = IP_VERSION_MAX_MASK_LEN[self.ip_version] + route_found = False + + for vrid in self.vrid_list: + for mask_len in range(max_mask_len, 0, -1): + network_addr = create_network_addr(ip_addr, mask_len, ip_version) + ecmp_id = sx_router_get_ecmp_id(self.handle, vrid, network_addr) + if ecmp_id != SX_ENTRY_NOT_FOUND: + route_found = True + self.debug_print("Found route for destination IP {} ECMP id {} VRID {}".format(self.dst_ip, ecmp_id, vrid)) + self.ecmp_ids[vrid] = ecmp_id + + # move to next vrid + break + + if not route_found: + raise EcmpCalcExit("No route found for given packet") + + def get_next_hops(self): + next_hops = [] + ecmp_found = False + + for vrid in self.ecmp_ids.keys(): + ecmp_id = self.ecmp_ids[vrid] + next_hops = sx_router_ecmp_nexthops_get(self.handle, ecmp_id) + + if len(next_hops) > 1: + if self.debug: + next_hops_ips = [] + for nh in next_hops: + ip = nh.next_hop_key.next_hop_key_entry.ip_next_hop.address + next_hops_ips.append(sx_ip_addr_to_str(ip)) + print("Next hops IPs {}, VRID {}".format(next_hops_ips, vrid)) + print("Found ECMP for destination IP {} ECMP id {}, now checking if port is member in VRF {}". + format(self.dst_ip, ecmp_id, 'default' if self.user_vrf=='' else self.user_vrf)) + + self.next_hops[vrid] = next_hops + ecmp_found = True + + if not ecmp_found: + raise EcmpCalcExit("No ECMP for given packet") + + def calculate_egress_port(self): + for vrid in self.vrid_list: + if vrid not in self.next_hops.keys(): + continue + + next_hops = self.next_hops[vrid] + next_hop_idx = self.get_next_hop_index(len(next_hops)) + next_hop = next_hops[next_hop_idx] + + rif = next_hop.next_hop_key.next_hop_key_entry.ip_next_hop.rif + ip = next_hop.next_hop_key.next_hop_key_entry.ip_next_hop.address + rif_params = sx_get_router_interface(self.handle, vrid, rif) + + self.debug_print("Next hop ip to which trafic will egress: {}".format(sx_ip_addr_to_str(ip))) + + # Handle router port + if PORT in rif_params: + logical = rif_params[PORT] + port_type = PORT + vlan_id = 0 + + # Handle vlan subinterface + elif VPORT in rif_params: + logical, vlan_id = sx_port_vport_base_get(self.handle, rif_params[VPORT]) + port_type = VPORT + + # Handle vlan interface + elif VLAN in rif_params: + vlan_id = rif_params[VLAN] + neigh_mac = sx_router_neigh_get_mac(self.handle, rif, ip) + if neigh_mac is not None: + mac_entry = sx_fdb_uc_mac_addr_get(self.handle, vlan_id, neigh_mac) + if mac_entry is not None: + logical = mac_entry.log_port + port_type = VLAN + + # Handle flood case + if (neigh_mac is None) or (mac_entry is None): + vlan_members = sx_vlan_ports_get(self.handle, rif_params[VLAN]) + for port in vlan_members: + if is_lag(port): + port = self.get_lag_member(port, True) + self.egress_ports.append(self.ports_map[port]) + return + + # Check if port is binded to VRF we got from the user + if is_lag(logical): + lag_logical = logical + logical = self.get_lag_member(lag_logical) + egress_port = self.ports_map[logical] + + port_channel = self.get_port_channel_name(egress_port) + if self.is_port_bind_to_user_vrf(port_type, port_channel, vlan_id): + self.egress_ports.append(egress_port) + return + else: + egress_port = self.ports_map[logical] + if self.is_port_bind_to_user_vrf(port_type, egress_port, vlan_id): + self.egress_ports.append(egress_port) + return + + def print_egress_port(self): + if len(self.egress_ports) == 0: + print("Egress port not found, check input parameters") + elif len(self.egress_ports) == 1: + print("Egress port: {}".format(self.egress_ports[0])) + else: + egress_ports = '' + for port in self.egress_ports: + egress_ports += ' ' + port + print("Egress ports:{}".format(egress_ports)) + + def is_port_bind_to_user_vrf(self, port_type, port, vlan_id = 0): + if port_type == PORT: + # INTF_TABLE:Ethernet0 + entry = '{}:{}'.format(INTF_TABLE, port) + elif port_type == VPORT: + # INTF_TABLE:Ethernet0.300 + entry = '{}:{}.{}'.format(INTF_TABLE, port, vlan_id) + elif port_type == VLAN: + # INTF_TABLE:Vlan300 + entry = '{}:Vlan{}'.format(INTF_TABLE, vlan_id) + + port_vrf = exec_cmd(['/usr/bin/redis-cli', '-n', '0', 'hget', entry, 'vrf_name']) + if self.user_vrf == port_vrf.strip(): + return True + + return False + + # Get port-channel name for given port-channel member port + def get_port_channel_name(self, port): + query_output = exec_cmd(['/usr/bin/redis-cli', '-n', '0', 'keys','*LAG_MEMBER_TABLE*']) + for line in query_output.split('\n'): + if str(port) in line: + port_channel = line.split(':')[PORT_CHANNEL_IDX] + return port_channel + + raise KeyError("Failed to get port-channel name for interface {}".format(port)) + + def get_ingress_port_logical_idx(self): + for logical_index, sonic_port_name in self.ports_map.items(): + if sonic_port_name == self.ingress_port: + return logical_index + + raise KeyError("Failed to get logical index for interface {}".format(self.ingress_port)) + + # Get index in next hop array from which packet will egress + def get_next_hop_index(self, ecmp_size): + logical = self.get_ingress_port_logical_idx() + + ecmp_hash = { + "ingress_port": str(hex(logical)), + "packet_info":self.packet['packet_info'], + "ecmp_size": ecmp_size, + } + + self.debug_print("Calling hash calculator for ECMP") + hash_result = self.call_hash_calculator({'ecmp_hash': ecmp_hash}) + ecmp_hash_result = hash_result['ecmp_hash'] + index = ecmp_hash_result['ecmp_index'] + + self.debug_print("Next hop index to which trafic will egress: {}".format(index)) + return index + + # Get index in LAG memebrs array from which packet will egress + def get_lag_member_index(self, lag_size, flood_case = False): + logical = self.get_ingress_port_logical_idx() + + lag_hash = { + "ingress_port": str(hex(logical)), + "packet_info": self.packet['packet_info'], + "lag_size": lag_size, + } + + self.debug_print("Calling hash calculator for LAG, flood case {}".format(True if flood_case else False)) + hash_result = self.call_hash_calculator({"lag_hash": lag_hash}) + lag_hash_result = hash_result["lag_hash"] + + if flood_case: + index = lag_hash_result['lag_mc_index'] + else: + index = lag_hash_result['lag_index'] + + self.debug_print("Lag member index from which trafic will egress: {}".format(index)) + return index + + # Get LAG memebr from which packet will egress + def get_lag_member(self, logical, flood_case = False): + lag_members = sx_lag_port_group_get(self.handle, logical) + lag_members.sort() + + member_index = self.get_lag_member_index(len(lag_members), flood_case) + lag_member = lag_members[member_index] + + self.debug_print("Lag member from which trafic will egress: {}".format(lag_member)) + return lag_member + + def call_hash_calculator(self, input_dict): + with open(HASH_CALC_INPUT_FILE, "w") as outfile: + json.dump(input_dict, outfile) + + out = exec_cmd([HASH_CALC_PATH, '-c', HASH_CALC_INPUT_FILE, '-o', HASH_CALC_OUTPUT_FILE, '-d']) + self.debug_print ("Hash calculator output:\n{}".format(out)) + + with open(HASH_CALC_OUTPUT_FILE, 'r') as openfile: + output_dict = json.loads(openfile.read()) + + return output_dict + + def get_active_vrids(self): + self.vrid_list = sx_get_active_vrids(self.handle) + + def validate_ipv4_header(self, header): + for ip in ['sip', 'dip']: + if ip in header and is_ip_valid(header[ip], IP_VERSION_IPV4) == False: + raise ValueError("Json validation failed: invalid IP {}".format(header[ip])) + + def validate_ipv6_header(self, header): + for ip in ['sip', 'dip']: + if ip in header and is_ip_valid(header[ip], IP_VERSION_IPV6) == False: + raise ValueError("Json validation failed: invalid IP {}".format(header[ip])) + + def validate_layer2_header(self, header): + for mac in ['smac', 'dmac']: + if mac in header and is_mac_valid(header[mac]) == False: + raise ValueError("Json validation failed: invalid mac {}".format(header[mac])) + + def validate_header(self, header, is_outer_header=False): + ipv4_header = False + ipv6_header = False + + # Verify IPv4 and IPv6 headers do not co-exist in header + if 'ipv4' in header: + ipv4_header = True + if 'ipv6' in header: + ipv6_header = True + + if ipv4_header and ipv6_header: + raise ValueError("Json validation failed: IPv4 and IPv6 headers can not co-exist") + + if ipv4_header: + # Verify valid IPs in header + self.validate_ipv4_header(header['ipv4']) + + if is_outer_header: + if 'dip' not in header['ipv4']: + raise ValueError("Json validation failed: destination IP is mandatory") + + self.dst_ip = header['ipv4']['dip'] + self.ip_version = IP_VERSION_IPV4 + + if 'tcp_udp' in header and 'proto' not in header['ipv4']: + raise ValueError("Json validation failed: transport protocol (proto) is mandatory when transport layer port exists") + + elif ipv6_header: + self.validate_ipv6_header(header['ipv6']) + + if is_outer_header: + if 'dip' not in header['ipv6']: + raise ValueError("Json validation failed: destination IP is mandatory") + + self.dst_ip = header['ipv6']['dip'] + self.ip_version = IP_VERSION_IPV6 + + if 'tcp_udp' in header and 'next_header' not in header['ipv6']: + raise ValueError("Json validation failed: transport protocol (next_header) is mandatory when transport layer port exists") + + # Verify valid macs in header + if header['layer2']: + self.validate_layer2_header(header['layer2']) + + def validate_outer_header(self): + outer_header = self.packet['packet_info'].get('outer') + if not outer_header: + raise ValueError("Json validation failed: outer header is mandatory") + + self.validate_header(outer_header, is_outer_header=True) + + def validate_inner_header(self): + inner_header = self.packet['packet_info'].get('inner') + if not inner_header: + return + + self.validate_header(inner_header) + + def validate_packet_json(self, packet_json): + # Verify json has valid format + self.packet = load_json(packet_json) + + # Verify json schema + try: + jsonschema.validate(self.packet, PACKET_SCHEME) + except jsonschema.exceptions.ValidationError as e: + raise ValueError("Json validation failed: {}".format(e)) + + # Verify outer header + self.validate_outer_header() + + # Verify inner header + self.validate_inner_header() + + if self.debug: + print('Packet:') + pprint.pprint(self.packet) + +def main(): + rc = 0 + try: + parser = argparse.ArgumentParser(description="ECMP calculator") + parser.add_argument("-i", "--interface", required=True, help="Ingress interface") + parser.add_argument("-p", "--packet", required=True, help="Packet description") + parser.add_argument("-v", "--vrf", help="VRF name") + parser.add_argument("-d", "--debug", default=False, action="store_true", help="Flag for debug") + args = parser.parse_args() + + ecmp_calc = EcmpCalc() + ecmp_calc.validate_args(args.interface, args.packet, args.vrf, args.debug) + ecmp_calc.get_ecmp_id() + ecmp_calc.get_next_hops() + ecmp_calc.calculate_egress_port() + ecmp_calc.print_egress_port() + + except EcmpCalcExit as s: + print(s) + except ValueError as s: + print("Value error: {}".format(s)) + rc = 1 + except Exception as s: + print("Error: {}".format(s)) + rc = 2 + return rc + +if __name__ == "__main__": + sys.exit(main()) diff --git a/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc_sdk.py b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc_sdk.py new file mode 100755 index 000000000000..564b2ffb02ba --- /dev/null +++ b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/ecmp_calc_sdk.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python3 + +from python_sdk_api.sx_api import * +from port_utils import sx_check_rc, SWITCH_ID +import struct +import socket +import ctypes +import sys + +PORT='port' +VPORT='vport' +VLAN='vlan' +SX_ENTRY_NOT_FOUND = -1 +SDK_DEFAULT_LOG_LEVEL=SX_VERBOSITY_LEVEL_ERROR + +def severity_to_verbosity(severity): + value = severity + 1 + verbosity = 0 + while not (value & 1): + value = value >> 1 + verbosity = verbosity + 1 + return verbosity + +def log_cb(severity, module_name, msg): + verbosity = severity_to_verbosity(severity) + log_level = {SX_VERBOSITY_LEVEL_INFO: 'LOG_INFO', SX_VERBOSITY_LEVEL_ERROR: 'LOG_ERR', + SX_VERBOSITY_LEVEL_WARNING: 'LOG_WARN', SX_VERBOSITY_LEVEL_DEBUG: 'LOG_DEBUG', + SX_VERBOSITY_LEVEL_NOTICE: 'LOG_NOTICE'} + if verbosity in log_level.keys(): + level = log_level[verbosity] + else: + level = 'LOG' + if verbosity <= SDK_DEFAULT_LOG_LEVEL: + print('{}: {}'.format(level, ctypes.cast(msg,ctypes.c_char_p).value.decode('utf-8'))) + +def sx_open_sdk_connection(): + """ Open connection to SDK + + Args: + None + + Returns: + SDK handle + """ + sx_api = ctypes.CDLL('libsxapi.so', mode=ctypes.RTLD_GLOBAL) + log_cb_type = ctypes.CFUNCTYPE(None, ctypes.c_int, + ctypes.POINTER(ctypes.c_char), + ctypes.POINTER(ctypes.c_char)) + log_cb_fn = log_cb_type(log_cb) + handle_p = ctypes.pointer(ctypes.c_int64()) + + rc = sx_api.sx_api_open(log_cb_fn, handle_p) + sx_check_rc(rc) + + return handle_p.contents.value + +def sx_close_sdk_connection(handle): + """ Close connection to SDK + + Args: + SDK handle + """ + rc = sx_api_close(handle) + sx_check_rc(rc) + +def sx_get_active_vrids(handle): + """ Get existing virtual router IDs + + Args: + handle (sx_api_handle_t): SDK handle + + Returns: + list : List of virtual routers ids + """ + try: + vrid_list = [] + + vrid_cnt_p = new_uint32_t_p() + uint32_t_p_assign(vrid_cnt_p, 0) + vrid_key_p = new_sx_router_id_t_p() + sx_router_id_t_p_assign(vrid_key_p, 0) + vrid_key = sx_router_id_t_p_value(vrid_key_p) + + rc = sx_api_router_vrid_iter_get(handle, SX_ACCESS_CMD_GET, vrid_key, None, None, vrid_cnt_p) + sx_check_rc(rc) + + vrid_cnt = uint32_t_p_value(vrid_cnt_p) + vrid_list_p = new_sx_router_id_t_arr(vrid_cnt) + + rc = sx_api_router_vrid_iter_get(handle, SX_ACCESS_CMD_GET_FIRST, vrid_key, None, vrid_list_p, vrid_cnt_p) + sx_check_rc(rc) + + vrid_cnt = uint32_t_p_value(vrid_cnt_p) + for i in range(0, vrid_cnt): + vrid = sx_router_id_t_arr_getitem(vrid_list_p, i) + vrid_list.append(vrid) + + return vrid_list + + finally: + delete_sx_router_id_t_arr(vrid_list_p) + delete_sx_router_id_t_p(vrid_key_p) + delete_uint32_t_p(vrid_cnt_p) + +def sx_router_get_ecmp_id(handle, vrid, ip_prefix): + """ Get ECMP id for a given IP prefix + + Args: + handle (sx_api_handle_t): SDK handle + vrid (sx_router_id_t): Virtual router id + ip_prefix (sx_ip_prefix_t): Network address + + Returns: + int: ECMP id + """ + try: + ip_prefix_p = new_sx_ip_prefix_t_p() + sx_ip_prefix_t_p_assign(ip_prefix_p, ip_prefix) + entries_cnt_p = new_uint32_t_p() + uint32_t_p_assign(entries_cnt_p, 1) + entries_array = new_sx_uc_route_get_entry_t_arr(1) + + rc = sx_api_router_uc_route_get(handle, SX_ACCESS_CMD_GET, vrid, ip_prefix_p, None, entries_array, entries_cnt_p) + if rc == SX_STATUS_ENTRY_NOT_FOUND: + return SX_ENTRY_NOT_FOUND + sx_check_rc(rc) + + entry = sx_uc_route_get_entry_t_arr_getitem(entries_array, 0) + if entry.route_data.type == SX_UC_ROUTE_TYPE_NEXT_HOP: + return entry.route_data.uc_route_param.ecmp_id + + return SX_ENTRY_NOT_FOUND + + finally: + delete_sx_uc_route_get_entry_t_arr(entries_array) + delete_uint32_t_p(entries_cnt_p) + delete_sx_ip_prefix_t_p(ip_prefix_p) + +def sx_router_ecmp_nexthops_get(handle, ecmp_id): + """ Get next hops for a given ECMP id + + Args: + handle (sx_api_handle_t): SDK handle + ecmp_id (int): ECMP id + + Returns: + list: List of next hops + """ + try: + next_hops = [] + + next_hop_count_p = new_uint32_t_p() + uint32_t_p_assign(next_hop_count_p, 0) + + rc = sx_api_router_operational_ecmp_get(handle, ecmp_id, None, next_hop_count_p) + sx_check_rc(rc) + + next_hop_count = uint32_t_p_value(next_hop_count_p) + next_hop_list_p = new_sx_next_hop_t_arr(next_hop_count) + + rc = sx_api_router_operational_ecmp_get(handle, ecmp_id, next_hop_list_p, next_hop_count_p) + sx_check_rc(rc) + + next_hop_count = uint32_t_p_value(next_hop_count_p) + for i in range(next_hop_count): + next_hop = sx_next_hop_t_arr_getitem(next_hop_list_p, i) + if next_hop.next_hop_key.type == SX_NEXT_HOP_TYPE_IP: + next_hops.append(next_hop) + + return next_hops + + finally: + delete_sx_next_hop_t_arr(next_hop_list_p) + delete_uint32_t_p(next_hop_count_p) + +def sx_get_router_interface(handle, vrid, rif): + """ Get router interface information + + Args: + handle (sx_api_handle_t): SDK handle + vrid (sx_router_id_t): virtual router id + rif (sx_router_interface_t): router interface id + + Returns: + dict : Dictionary contains interface parameters + """ + try: + vrid_p = new_sx_router_id_t_p() + sx_router_id_t_p_assign(vrid_p, vrid) + + ifc_p = new_sx_router_interface_param_t_p() + ifc_attr_p = new_sx_interface_attributes_t_p() + rif_params = {} + + rc = sx_api_router_interface_get(handle, rif, vrid_p, ifc_p, ifc_attr_p) + sx_check_rc(rc) + + if ifc_p.type == SX_L2_INTERFACE_TYPE_PORT_VLAN: + rif_params[PORT] = ifc_p.ifc.port_vlan.port + + if ifc_p.type == SX_L2_INTERFACE_TYPE_VPORT: + rif_params[VPORT] = ifc_p.ifc.vport.vport + + if ifc_p.type == SX_L2_INTERFACE_TYPE_VLAN: + rif_params[VLAN] = ifc_p.ifc.vlan.vlan + + return rif_params + + finally: + delete_sx_interface_attributes_t_p(ifc_attr_p) + delete_sx_router_interface_param_t_p(ifc_p) + delete_sx_router_id_t_p(vrid_p) + +def sx_port_vport_base_get(handle, vport): + """ Get SDK logical index and vlan for given vport + + Args: + handle (sx_api_handle_t): SDK handle + vport (sx_port_id_t): SDK vport id + + Returns: + sx_port_log_id_t : SDK logical index + """ + try: + vlan_id_p = new_sx_vlan_id_t_p() + logical_port_p = new_sx_port_log_id_t_p() + + rc = sx_api_port_vport_base_get(handle, vport, vlan_id_p, logical_port_p) + sx_check_rc(rc) + + logical_port = sx_port_log_id_t_p_value(logical_port_p) + vlan_id = sx_vlan_id_t_p_value(vlan_id_p) + + return logical_port, vlan_id + + finally: + delete_sx_port_log_id_t_p(logical_port_p) + delete_sx_vlan_id_t_p(vlan_id_p) + +def sx_router_neigh_get_mac(handle, rif, addr): + """ Get neighbour mac address + + Args: + handle (sx_api_handle_t): SDK handle + rif (sx_port_id_t): SDK vport id + addr (sx_ip_addr_t): Neighbour IP address + + Returns: + str : Neighbour mac address + """ + try: + neigh_entry_cnt_p = new_uint32_t_p() + neigh_entry_list_p = new_sx_neigh_get_entry_t_arr(1) + + filter_p = new_sx_neigh_filter_t_p() + neigh_filter = sx_neigh_filter_t() + neigh_filter.filter_by_rif = SX_KEY_FILTER_FIELD_NOT_VALID + neigh_filter.rif = 0 + sx_neigh_filter_t_p_assign(filter_p, neigh_filter) + + rc = sx_api_router_neigh_get(handle, SX_ACCESS_CMD_GET, rif, addr, filter_p, neigh_entry_list_p, neigh_entry_cnt_p) + if rc == SX_STATUS_ENTRY_NOT_FOUND: + return None + sx_check_rc(rc) + + neighbor_entry = sx_neigh_get_entry_t_arr_getitem(neigh_entry_list_p, 0) + + return neighbor_entry.neigh_data.mac_addr.to_str() + + finally: + delete_sx_neigh_filter_t_p(filter_p) + delete_sx_neigh_get_entry_t_arr(neigh_entry_list_p) + delete_uint32_t_p(neigh_entry_cnt_p) + +def sx_fdb_uc_mac_addr_get(handle, vlan_id, mac_addr): + """ Get UC mac entry from FDB + + Args: + handle (sx_api_handle_t): SDK handle + vlan_id (sx_vlan_id_t): VLAN id + mac_addr (str): mac address + + Returns: + sx_fdb_uc_mac_addr_params_t : FDB mac entry + """ + try: + key = sx_fdb_uc_mac_addr_params_t() + key.fid_vid = vlan_id + key.mac_addr = ether_addr(mac_addr) + key.action = SX_FDB_ACTION_FORWARD + key_p = copy_sx_fdb_uc_mac_addr_params_t_p(key) + + key_filter = sx_fdb_uc_key_filter_t() + key_filter.filter_by_fid = SX_FDB_KEY_FILTER_FIELD_VALID + key_filter.filter_by_mac_addr = SX_FDB_KEY_FILTER_FIELD_VALID + key_filter.filter_by_log_port = SX_FDB_KEY_FILTER_FIELD_NOT_VALID + key_filter.fid = vlan_id + key_filter.mac_addr = ether_addr(mac_addr) + key_filter_p = copy_sx_fdb_uc_key_filter_t_p(key_filter) + + data_cnt_p = copy_uint32_t_p(SX_FDB_MAX_GET_ENTRIES) + mac_list_p = new_sx_fdb_uc_mac_addr_params_t_arr(SX_FDB_MAX_GET_ENTRIES) + + rc = sx_api_fdb_uc_mac_addr_get(handle, 0, SX_ACCESS_CMD_GET_FIRST, SX_FDB_UC_ALL, key_p, key_filter_p, mac_list_p, data_cnt_p) + if rc == SX_STATUS_ENTRY_NOT_FOUND: + return None + sx_check_rc(rc) + + data_cnt = uint32_t_p_value(data_cnt_p) + if data_cnt == 0: + return None + + assert data_cnt == 1, "Got unexpected macs amount, mac {} vlan {} data_cnt {}".format(mac_addr, vlan_id, data_cnt) + + mac_entry = sx_fdb_uc_mac_addr_params_t_arr_getitem(mac_list_p, 0) + assert mac_entry.dest_type == SX_FDB_UC_MAC_ADDR_DEST_TYPE_LOGICAL_PORT, "Got unexpected mac entry type {}".format(mac_entry.dest_type) + + return mac_entry + + finally: + delete_sx_fdb_uc_mac_addr_params_t_arr(mac_list_p) + delete_uint32_t_p(data_cnt_p) + delete_sx_fdb_uc_key_filter_t_p(key_filter_p) + delete_sx_fdb_uc_mac_addr_params_t_p(key_p) + +def sx_lag_port_group_get(handle, lag_id): + """ Get LAG members + + Args: + handle (sx_api_handle_t): SDK handle + lag_id (sx_port_log_id_t): LAG id + + Returns: + list : list of LAG members logical indices + """ + try: + lag_members = [] + port_count_p = new_uint32_t_p() + uint32_t_p_assign(port_count_p, 0) + port_arr = None + + rc = sx_api_lag_port_group_get(handle, 0, lag_id, port_arr, port_count_p) + sx_check_rc(rc) + + port_count = uint32_t_p_value(port_count_p) + if port_count > 0: + port_arr = new_sx_port_log_id_t_arr(port_count) + rc = sx_api_lag_port_group_get(handle, 0, lag_id, port_arr, port_count_p) + sx_check_rc(rc) + + for i in range(port_count): + lag_members.append(sx_port_log_id_t_arr_getitem(port_arr, i)) + + return lag_members + + finally: + delete_sx_port_log_id_t_arr(port_arr) + delete_uint32_t_p(port_count_p) + +def sx_vlan_ports_get(handle, vlan_id): + """ Get VLAN member ports + Args: + handle (sx_api_handle_t): SDK handle + vlan_id (sx_vid_t): VLAN id + + Returns: + list : list of VLAN members logical indexes + """ + try: + vlan_members = [] + port_cnt_p = new_uint32_t_p() + uint32_t_p_assign(port_cnt_p, 0) + + rc = sx_api_vlan_ports_get(handle, SWITCH_ID, vlan_id, None, port_cnt_p) + sx_check_rc(rc) + + port_cnt = uint32_t_p_value(port_cnt_p) + vlan_port_list_p = new_sx_vlan_ports_t_arr(port_cnt) + + rc = sx_api_vlan_ports_get(handle, SWITCH_ID, vlan_id, vlan_port_list_p, port_cnt_p) + sx_check_rc(rc) + + for i in range(0, port_cnt): + vlan_port = sx_vlan_ports_t_arr_getitem(vlan_port_list_p, i) + vlan_members.append(vlan_port.log_port) + + return vlan_members + + finally: + delete_sx_vlan_ports_t_arr(vlan_port_list_p) + delete_uint32_t_p(port_cnt_p) + +def sx_make_ip_prefix_v4(addr, mask): + """ Create IPv4 prefix + + Args: + addr (str): IPv4 address + mask (str): Network mask + + Returns: + sx_ip_prefix_t : IPv4 prefix + """ + ip_prefix = sx_ip_prefix_t() + ip_prefix.version = SX_IP_VERSION_IPV4 + ip_prefix.prefix.ipv4.addr.s_addr = struct.unpack('>I', socket.inet_pton(socket.AF_INET, addr))[0] + ip_prefix.prefix.ipv4.mask.s_addr = struct.unpack('>I', socket.inet_pton(socket.AF_INET, mask))[0] + + return ip_prefix + +def sx_make_ip_prefix_v6(addr, mask): + """ Create IPv6 prefix + + Args: + addr (str): IPv6 address + mask (str): Network mask + + Returns: + sx_ip_v6_prefix_t : IPv6 prefix + """ + addr = ipv6_str_to_bytes(str(addr)) + mask = ipv6_str_to_bytes(str(mask)) + + ip_prefix = sx_ip_prefix_t() + ip_prefix.version = SX_IP_VERSION_IPV6 + for i in range(0, 16): + uint8_t_arr_setitem(ip_prefix.prefix.ipv6.addr._in6_addr__in6_u._in6_addr___in6_u__u6_addr8, i, addr[i]) + uint8_t_arr_setitem(ip_prefix.prefix.ipv6.mask._in6_addr__in6_u._in6_addr___in6_u__u6_addr8, i, mask[i]) + + return ip_prefix + +def ipv6_str_to_bytes(address): + # Load the ipv6 string into 4 dwords + dwords = struct.unpack("!IIII", socket.inet_pton(socket.AF_INET6, address)) + # Convert dwords endian using ntohl + total_bytes = [socket.ntohl(i) for i in dwords] + # Finally, convert back to bytes + out = struct.pack(">IIII", total_bytes[0], total_bytes[1], total_bytes[2], total_bytes[3]) + return [i for i in out] + +def sx_ip_addr_to_str(ip_addr): + if ip_addr.version == SX_IP_VERSION_IPV4: + return socket.inet_ntop(socket.AF_INET, struct.pack('!I', ip_addr.addr.ipv4.s_addr)) + else: + bytes = ip_addr.addr.ipv6._in6_addr__in6_u._in6_addr___in6_u__u6_addr8 + byte_arr = [] + for i in range(0, 16): + byte_arr.append(uint8_t_arr_getitem(bytes, i)) + if sys.byteorder == "little": + dwords = struct.pack("!BBBBBBBBBBBBBBBB", byte_arr[3], byte_arr[2], byte_arr[1], byte_arr[0], byte_arr[7], + byte_arr[6], byte_arr[5], byte_arr[4], byte_arr[11], byte_arr[10], byte_arr[9], + byte_arr[8], byte_arr[15], byte_arr[14], byte_arr[13], byte_arr[12]) + else: + dwords = struct.pack("!BBBBBBBBBBBBBBBB", byte_arr[0], byte_arr[1], byte_arr[2], byte_arr[3], byte_arr[4], + byte_arr[5], byte_arr[6], byte_arr[7], byte_arr[8], byte_arr[9], byte_arr[10], + byte_arr[11], byte_arr[12], byte_arr[13], byte_arr[14], byte_arr[15]) + return socket.inet_ntop(socket.AF_INET6, dwords) diff --git a/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/packet_scheme.py b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/packet_scheme.py new file mode 100755 index 000000000000..922669f42aaf --- /dev/null +++ b/platform/mellanox/docker-syncd-mlnx/ecmp_calculator/packet_scheme.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +PACKET_SCHEME = { + "type" : "object", + "properties" : + { + "packet_info" : + { + "type" : "object", + "properties" : + { + "outer" : + { + "type" : "object", + "properties" : + { + "layer2" : + { + "type" : "object", + "properties" : + { + "smac": {"type": "string"}, + "dmac": {"type": "string"}, + "ethertype": {"type": "number"}, + "outer_vid": {"type": "number"}, + "outer_pcp": {"type": "number"}, + "outer_dei": {"type": "number"}, + "inner_vid": {"type": "number"}, + "inner_pcp": {"type": "number"}, + "inner_dei": {"type": "number"} + } + }, + "arp" : + { + "type" : "object", + "properties" : + { + "spa": {"type": "string"}, + "tpa": {"type": "string"} + } + }, + "ipv4" : + { + "type" : "object", + "properties" : + { + "sip": {"type": "string"}, + "dip": {"type": "string"}, + "proto": {"type": "number"} + }, + "required": ["dip"] + }, + "ipv6" : + { + "type" : "object", + "properties" : + { + "sip": {"type": "string"}, + "dip": {"type": "string"}, + "mflag": {"type": "number"}, + "next_header": {"type": "number"}, + "dscp": {"type": "number"}, + "ecn": {"type": "number"}, + "l3_length": {"type": "number"}, + "flow_label": {"type": "number"} + }, + "required": ["dip"] + }, + "tcp_udp" : + { + "type" : "object", + "properties" : + { + "sport": {"type": "number"}, + "dport": {"type": "number"} + } + }, + "vxlan_nvgre" : + { + "type" : "object", + "properties" : + { + "vni": {"type": "number"} + } + } + } + }, + "inner" : + { + "type" : "object", + "properties" : + { + "layer2" : + { + "type" : "object", + "properties" : + { + "smac": {"type": "string"}, + "dmac": {"type": "string"}, + "ethertype": {"type": "number"} + } + }, + "ipv4" : + { + "type" : "object", + "properties" : + { + "sip": {"type": "string"}, + "dip": {"type": "string"}, + "mflag": {"type": "number"}, + "proto": {"type": "number"} + } + }, + "ipv6" : + { + "type" : "object", + "properties" : + { + "sip": {"type": "string"}, + "dip": {"type": "string"}, + "mflag": {"type": "number"}, + "next_header": {"type": "number"}, + "flow_label": {"type": "number"} + } + }, + "tcp_udp" : + { + "type" : "object", + "properties" : + { + "sport": {"type": "number"}, + "dport": {"type": "number"} + } + } + } + } + } + } + } +} diff --git a/platform/mellanox/docker-syncd-mlnx/lib/port_utils.py b/platform/mellanox/docker-syncd-mlnx/lib/port_utils.py new file mode 100755 index 000000000000..cce63298cf9a --- /dev/null +++ b/platform/mellanox/docker-syncd-mlnx/lib/port_utils.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +from python_sdk_api.sx_api import * +import inspect + +DEVICE_ID = 1 +SWITCH_ID = 0 +ETHERNET_PREFIX = 'Ethernet' +ASIC_MAX_LANES = {SX_CHIP_TYPE_SPECTRUM: 4, SX_CHIP_TYPE_SPECTRUM2: 4, + SX_CHIP_TYPE_SPECTRUM3: 8, SX_CHIP_TYPE_SPECTRUM4: 8} + +def sx_get_ports_map(handle): + """ Get ports map from SDK logical index to SONiC index + + Args: + handle (sx_api_handle_t): SDK handle + + Returns: + dict : Dictionary of ports indices. Key is SDK logical index, value is SONiC index (4 for Ethernet4) + """ + try: + ports_map = {} + + # Get chip type + chip_type = sx_get_chip_type(handle) + + # Get ports count + port_cnt_p = new_uint32_t_p() + rc = sx_api_port_device_get(handle, DEVICE_ID, SWITCH_ID, None, port_cnt_p) + sx_check_rc(rc) + + # Get ports + port_cnt = uint32_t_p_value(port_cnt_p) + port_attributes_list = new_sx_port_attributes_t_arr(port_cnt) + rc = sx_api_port_device_get(handle, DEVICE_ID, SWITCH_ID, port_attributes_list, port_cnt_p) + sx_check_rc(rc) + + for i in range(0, port_cnt): + port_attributes = sx_port_attributes_t_arr_getitem(port_attributes_list, i) + label_port = port_attributes.port_mapping.module_port + logical_port = port_attributes.log_port; + lane_bmap = port_attributes.port_mapping.lane_bmap; + + if (is_phy_port(port_attributes.log_port) == False): + continue + + # Calculate sonic index (sonic index=4 for Ethernet4) + lane_index = get_lane_index(lane_bmap, ASIC_MAX_LANES[chip_type]) + assert lane_index != -1, "Failed to calculate port index" + + sonic_index = label_port * ASIC_MAX_LANES[chip_type] + lane_index; + sonic_interface = ETHERNET_PREFIX + str(sonic_index) + ports_map[logical_port] = sonic_interface + + return ports_map + + finally: + delete_sx_port_attributes_t_arr(port_attributes_list) + delete_uint32_t_p(port_cnt_p) + +def sx_get_chip_type(handle): + """ Get system ASIC type + + Args: + handle (sx_api_handle_t): SDK handle + + Returns: + sx_chip_types_t : Chip type + """ + try: + device_info_cnt_p = new_uint32_t_p() + uint32_t_p_assign(device_info_cnt_p, 1) + device_info_cnt = uint32_t_p_value(device_info_cnt_p) + device_info_list_p = new_sx_device_info_t_arr(device_info_cnt) + + rc = sx_api_port_device_list_get(handle, device_info_list_p, device_info_cnt_p) + sx_check_rc(rc) + + device_info = sx_device_info_t_arr_getitem(device_info_list_p, SWITCH_ID) + chip_type = device_info.dev_type + if chip_type == SX_CHIP_TYPE_SPECTRUM_A1: + chip_type = SX_CHIP_TYPE_SPECTRUM + + return chip_type + + finally: + delete_sx_device_info_t_arr(device_info_list_p) + delete_uint32_t_p(device_info_cnt_p) + +def get_lane_index(lane_bmap, max_lanes): + """ Get index of first lane in use (2 for 00001100) + + Args: + lane_bmap (int): bitmap indicating module lanes in use + max_lanes (int): Max lanes in module + + Returns: + int : index of the first bit set to 1 in lane_bmap + """ + for lane_idx in range(0, max_lanes): + if (lane_bmap & 0x1 == 1): + return lane_idx + lane_bmap = lane_bmap >> 1 + +def sx_check_rc(rc): + if rc is not SX_STATUS_SUCCESS: + # Get the calling function name from the last frame + cf = inspect.currentframe().f_back + func_name = inspect.getframeinfo(cf).function + error_info = func_name + ' failed with rc = ' + str(rc) + + raise Exception(error_info) + +def get_port_type(log_port_id): + return (log_port_id & SX_PORT_TYPE_ID_MASK) >> SX_PORT_TYPE_ID_OFFS + +def is_phy_port(log_port_id): + return get_port_type(log_port_id) == SX_PORT_TYPE_NETWORK + +def is_lag(log_port_id): + return get_port_type(log_port_id) == SX_PORT_TYPE_LAG +