#
# Copyright (c) 2013-2024 NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#  This software is available to you under a choice of one of two
#  licenses.  You may choose to be licensed under the terms of the GNU
#  General Public License (GPL) Version 2, available from the file
#  COPYING in the main directory of this source tree, or the
#  OpenIB.org BSD license below:
#
#      Redistribution and use in source and binary forms, with or
#      without modification, are permitted provided that the following
#      conditions are met:
#
#       - Redistributions of source code must retain the above
#         copyright notice, this list of conditions and the following
#         disclaimer.
#
#       - Redistributions in binary form must reproduce the above
#         copyright notice, this list of conditions and the following
#         disclaimer in the documentation and/or other materials
#         provided with the distribution.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
#  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
#  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
#  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#  SOFTWARE.
#
# mst_ib_add :
# Parse the given lst file and print the device names for
# mst inband access
#
from __future__ import print_function
import sys
import argparse
import re
import collections
import csv
import glob
import json
import mtcr

SUNBIRD_DEV_PTRN = "MT5400"
LST_FILE_PATH = "/var/tmp/ibdiagnet2/ibdiagnet2.lst"
SYSTEM_IMG_GUID_TO_DEVICE_NAMES_FILE = "/tmp/system_img_guid_to_device_name.json"
NUM_OF_LIDS_AGGREGATED = 4


def create_aggregated_device(system_image_guid, lids, hca_id=None):
    flat_list = [item for sublist in lids for item in sublist]
    if len(flat_list) != NUM_OF_LIDS_AGGREGATED:
        raise ValueError("{} lids provided. Please provide exactly {} lids.".format(len(flat_list), NUM_OF_LIDS_AGGREGATED))

    if hca_id:
        flat_list = ["{},{}".format(lid, hca_id) for lid in flat_list]

    sysimgguid_to_lids = {system_image_guid: flat_list}

    with open(SYSTEM_IMG_GUID_TO_DEVICE_NAMES_FILE, 'w') as file:
        json.dump(sysimgguid_to_lids, file)


class CSVSection():
    """ a class representing a CSV section of the IBDiagnet CSV file"""

    """ init function:
        Args:
            section_start_name: str
            section_end_name: str
            section_column_names: list[str]
        Return:
            None: """
    def __init__(self, section_start_name, section_end_name, section_column_names):
        self.section_start_name = section_start_name
        self.section_end_name = section_end_name
        self.section_column_names = section_column_names


class _MstFileParser(object):

    MELLANOX_VEND_ID = "0x2c9"
    UNSUPPORTED_DEV_IDS = ['0xbd34', '0xbd35', '0xbd36', '0xfa66', '0xfa7a', '0x1003', '0x1007',
                           '48436', '48437', '48438', '64102', '64122', '4099', '4103']

    def __init__(self,
                 lst_file,
                 all_or_only_mlnx,
                 file_type,
                 ibdr,
                 hca_id,
                 exit_port_num,
                 print_with_guids=False,
                 aggregated=False):
        self._lst_file = lst_file
        self._all_or_only_mlnx = all_or_only_mlnx
        self._file_type = file_type
        self._ibdr = ibdr
        self._hca_id = hca_id
        self._exit_port_num = exit_port_num
        self._print_with_guids = print_with_guids
        self._aggregated = aggregated
        self._ib_node = collections.namedtuple("ib_node",
                                               ['node_guid', 'port_guid', 'lid', 'desc', 'type', 'dev_id', 'vend_id', 'dr_id'])
        self._ib_db = []

    def _add_nodes(self, node_data, dr_pattern=''):
        """
        Parse the node data and insert it to the IB DB
        """
        raise NotImplementedError("add_nodes() is not implemented")

    def parse_file(self):
        """
        Parse the file in order to extract the nodes data and send it for
        node parsing and db inserting
        """
        raise NotImplementedError("parse_file() is not implemented")

    def _is_ibdr_unsupported(self, dev_id):
        return str(dev_id) in self.UNSUPPORTED_DEV_IDS

    def _is_node_exist_in_db(self, node_guid):
        node_exist = False

        # check if given node guid is already inside the IB db
        # if found, stop searching and return the right indication
        for data in self._ib_db:
            if node_guid == data.node_guid:
                node_exist = True
                break

        return node_exist

    def _is_node_ignored(self, vend_id):
        # flag for the returned value
        is_ignored = False

        # if the given argument is only mlnx and the vend id is defined for ignore
        # need to return indication for ignoring
        if self._all_or_only_mlnx != "all" and self.MELLANOX_VEND_ID != vend_id:
            is_ignored = True

        return is_ignored

    def retrieve_all_mst_ib(self):
        """
        retrieve all the parsed ib data that added to the ib db
        :return: list of strings
        """
        # suffix to the end of the output
        suffix = ""
        # returned list of all the ib's parsed and added to the db
        output_list = []

        # set the suffix only if the user insert the hca and exit port arguments
        if self._hca_id != "":
            suffix += "," + self._hca_id
            if self._exit_port_num != "0":
                suffix += "," + self._exit_port_num

        # for each line in db, set the needed values at the output string in the right format
        for line in self._ib_db:
            # ded id will be converter to decimal
            output_str = line.type + "_MT" + str(int(line.dev_id, 16)) + "_"

            # adjust the description
            desc = str(line.desc)
            desc = re.sub(' ', '_', desc)           # remove all spaces
            if re.findall('^MF0;', desc):
                # remove the 'MFO;' from string
                desc = re.sub('^MF0;', '', desc)
                # remove the string from ':' to '/'
                desc = re.sub(':[^/]*', '', desc)
                desc = re.sub('/', '_', desc)       # change '/' to '_'
                desc = re.sub('_U1', '', desc)      # remove '_UI'
            output_str += desc + "_"

            # add the lid
            if self._ibdr and line.dr_id and not self._is_ibdr_unsupported(line.dr_id):
                output_str += "ibdr-" + line.dr_id
            else:
                output_str += "lid-" + line.lid

            # make sure we don't have bad chars in the dev name
            output_str = re.sub('[:;/]', '_', output_str)

            # prevent Mellanox Technologies Aggregation Nodes from being created
            if "Mellanox_Technologies_Aggregation_Node" in output_str:
                continue

            # if the 'print with guid' is true, need to add the port guid
            output_str += suffix
            if self._print_with_guids:
                output_str += "#" + line.port_guid
            if output_str not in output_list:
                output_list.append(output_str)

        return output_list


class _DiagnetParser(_MstFileParser):

    def _add_nodes(self, node_data, dr_pattern=''):
        # split for all lines in node data:
        lines = node_data.split(" }")

        # re return a list (in that case this method will refer to [0] index of each parsed parameter)
        for ib_data in lines:
            # parse the Node Guid
            node_guid = re.compile("NodeGUID:(\\S*)\\s").findall(ib_data)
            # parse the port Guid
            port_guid = re.compile("PortGUID:(\\S*)\\s").findall(ib_data)
            # parse the LID
            lid = re.compile("LID:([0-9a-f]*)\\s").findall(ib_data)
            # parse the description
            desc = re.compile("{(\\S[^}]*)}").findall(ib_data)
            # parse the Type
            type_ib = re.compile("{\\s(\\S*)").findall(ib_data)
            # parse the Dev Id
            dev_id = re.compile("DevID:(\\S*)\\s").findall(ib_data)
            # parse the Ven Id
            vend_id = re.compile("VenID:(\\S*)\\s").findall(ib_data)

            if node_guid != [] or \
                    port_guid != [] or \
                    lid != [] or \
                    desc != [] or \
                    type_ib != [] or \
                    dev_id != [] or \
                    vend_id != []:

                # adjust port data
                vend_id[0] = "0x" + vend_id[0].lstrip("0")
                dev_id[0] = "0x" + dev_id[0].lstrip("0")
                lid[0] = "{0:#0{1}x}".format(int(lid[0], 16), 6)

                if type_ib[0] == "CA-SM":
                    type_ib[0] = "CA"
                elif type_ib[0] == "SW-SM":
                    type_ib[0] = "SW"

                if self._is_node_ignored(vend_id[0]) or self._is_node_exist_in_db(node_guid):
                    continue

                if dev_id[0] != "43132" and not mtcr.isGPUPCIDeviceID(int(dev_id[0], 16)):
                    if not desc:
                        desc.append('')
                    ib_node = self._ib_node(node_guid[0], port_guid[0], lid[0], desc[0],
                                            type_ib[0], dev_id[0], vend_id[0], "")
                    self._ib_db.append(ib_node)

    def parse_file(self):
        if self._aggregated:
            self.parse_aggregated_file()  # Retrieve the info needed from ibdiagnet and create a file with the data, so that mst_tool will get the systemImageGUID and be able to create the aggregated device file

        else:
            # flag for the returned value
            active_links_found = False
            # check if line has an active link and send (each line) for parsing and inserting to DB
            try:
                with open(self._lst_file, 'r') as f:
                    for line in f:
                        if "LOG=ACT" in line:
                            active_links_found = True
                            self._add_nodes(line)
            except Exception as e:
                raise RuntimeError(e)

            return active_links_found

    def parse_aggregated_csv_section(self, section):
        """
            Args:
                section: CSVSection
        """
        return self.parse_aggregated_csv(section.section_start_name, section.section_end_name, section.section_column_names)

    def parse_aggregated_csv(self,
                             section_start_name,
                             section_end_name,
                             section_column_names):
        """parse a section of the CSV generated by the ibdiagnet.
        in the CSV, each section has a starting & ending line.

        Args:
            section_start_name (str): string indicating section start in the CSV
            section_end_name (str): string indicating section end in the CSV
            section_columns (list[str]): columns needed to be extracted for the section

        Returns:
            dict: dictionary containing the relevant values from the requested cols in the section
        """

        results = {}
        section_columns = {}

        for col_name in section_column_names:
            section_columns[col_name] = None

        sub_table_header = False
        started_section = False

        with open(self._lst_file, 'r') as file:
            csv_reader = csv.reader(file)
            # Skip the header row if it exists
            next(csv_reader, None)
            for row in csv_reader:
                cols = '\t'.join(row).split('\t')
                if cols[0] == section_start_name:
                    sub_table_header = True
                elif cols[0] == section_end_name:
                    started_section = False
                elif sub_table_header:
                    for key in section_columns.keys():
                        section_columns[key] = cols.index(key)
                    sub_table_header = False
                    started_section = True
                elif started_section:
                    mapping = {}
                    for column, offset in section_columns.items():
                        mapping[column] = cols[offset]
                    try:
                        results[mapping['PortGUID']] = mapping
                    except BaseException:
                        results[mapping['PortGuid']] = mapping
        return results

    def parse_lst_file(self):
        """parse the lst file created by ibdiagnet.

        Returns:
            result: dictionary where the key is the PortGUID and the value is a dictionary with the PortGUID and LID
        """
        result = {}
        port_guid_pattern = re.compile(r'PortGUID:(\w+)')
        lid_pattern = re.compile(r'LID:(\w+)')

        with open(LST_FILE_PATH, 'r') as file:
            for line in file:
                # Check if "LOG=ACT" is in the line since we want the active ports
                if "LOG=ACT" in line:
                    port_guids = port_guid_pattern.findall(line)
                    lids = lid_pattern.findall(line)
                    # Ensure we have equal numbers of PortGUIDs and LIDs
                    if len(port_guids) == len(lids):
                        for port_guid, lid in zip(port_guids, lids):
                            lid_decimal = int(lid, 16)
                            port_guid_key = "0x" + port_guid
                            result[port_guid_key] = {
                                "PortGUID": port_guid_key,
                                "LID": lid_decimal
                            }
        return result

    def extract_lid(self, dev):
        # Regular expression pattern to match "_lid-" followed by a decimal number/hexadecimal number which starts with 0x
        lid_pattern_in_dev_name = r"_lid-(0x[0-9a-fA-F]+|\d+)"
        match = re.search(lid_pattern_in_dev_name, dev)

        if match:
            lid_str = match.group(1)

            if lid_str.startswith("0x"):
                # Convert hexadecimal to decimal
                lid = int(lid_str, 16)
            else:
                # Convert decimal string to integer
                lid = int(lid_str, 10)
            return lid
        else:
            return None

    def lid2switch_name(self):
        switches_list = []
        lid2switch_name = {}
        for dev in glob.glob("/dev/mst/*"):
            if SUNBIRD_DEV_PTRN in dev and self._hca_id in dev:
                switches_list.append(dev)
        for switch in switches_list:
            try:
                current_lid = self.extract_lid(switch)
                lid2switch_name[current_lid] = switch
            except Exception as e:
                print("Failed to handle %s : Err: %s" % (switch, e))
        return lid2switch_name  # returns dictionary: { LID : switch_name }

    def get_system_img_guid_to_device_names(self, port_guid_mapping, lid2switch_name):
        ret = {}
        ib_attributes = port_guid_mapping.values()
        for attribute in ib_attributes:
            current_lid = attribute.get('LID')
            current_system_img_guid = attribute.get('SystemImageGUID')
            if current_lid is not None and int(current_lid) in lid2switch_name:
                current_switch_name = lid2switch_name.get(current_lid)
                if current_system_img_guid in ret:
                    ret[current_system_img_guid].append(current_switch_name)
                else:
                    ret[current_system_img_guid] = [current_switch_name]
        return ret

    def parse_aggregated_file(self):
        port_guid2lid = self.parse_lst_file()  # get LID info from .lst file
        # Parse the Nodes Section.
        start_nodes_section = CSVSection('START_NODES', 'END_NODES', ['PortGUID', 'NodeGUID', 'SystemImageGUID', 'DeviceID'])
        port_guid_mapping = self.parse_aggregated_csv_section(start_nodes_section)

        for port_guid, lid in port_guid2lid.items():
            port_guid_mapping[port_guid]['LID'] = lid['LID']

        # Parse the Port Hierarchy Info section and add the info to the original mapping.
        port_hierarchy_info_section = CSVSection('START_PORT_HIERARCHY_INFO', 'END_PORT_HIERARCHY_INFO', ['PortGUID', 'Cage', 'Port', 'Split', 'IsCageManager', 'IBPort', 'Plane', 'ASIC'])
        port2hierarchy = self.parse_aggregated_csv_section(port_hierarchy_info_section)

        for port, hierarchyinfo in port2hierarchy.items():
            for col_name in port_hierarchy_info_section.section_column_names:
                port_guid_mapping[port][col_name] = hierarchyinfo[col_name]

        lid2switch = self.lid2switch_name()
        system_img_guid_to_device_names = self.get_system_img_guid_to_device_names(port_guid_mapping, lid2switch)
        with open(SYSTEM_IMG_GUID_TO_DEVICE_NAMES_FILE, 'w') as file:
            json.dump(system_img_guid_to_device_names, file)

        # # Filter out non-aggregated ports.
        # aggregated_ports = filter(is_aggregated_port, port_guid_mapping.values())

        # # Group the aggregated Ports by System Image Guid.
        # aports_grouped_by_sysimgguid = {}
        # for info in aggregated_ports:
        #     if info['SystemImageGUID'] not in aports_grouped_by_sysimgguid:
        #         aports_grouped_by_sysimgguid[info['SystemImageGUID']] = []
        #     aports_grouped_by_sysimgguid[info['SystemImageGUID']].append(info)

        # # Dump the aggregated ports into a JSON /dev/mst file.
        # for sysimgguid in aports_grouped_by_sysimgguid.keys():
        #     json_db = json.dumps(aports_grouped_by_sysimgguid)
        #     with open('/dev/mst/' + sysimgguid + '_aggregated_port', 'w+') as f:
        #         f.write(json_db)


class _NetParser(_MstFileParser):

    def _add_nodes(self, node_data, dr_pattern=''):
        # split to lines
        lines = node_data.split("\n")
        line_index = 0

        # re return a list (in that case this method will refer to [0] index of each parsed parameter)
        # Get vendor ID
        vend_id = re.compile("vendid=(\\S*)").findall(lines[line_index])
        line_index += 1
        if vend_id is None:
            raise Exception(self._lst_file + ":" + str(line_index) +
                            "Bad node format - Missing VendorId: " + lines[line_index])

        # Get Device ID
        dev_id = re.compile("devid=(\\S*)").findall(lines[line_index])
        line_index += 1
        if dev_id is None:
            raise Exception(self._lst_file + ":" + str(line_index) +
                            "Bad node format - Missing Device ID: " + lines[line_index])

        # Get node guid
        node_guid = re.compile(
            "(?:switch|ca)guid=([a-z0-9]*)").findall(lines[line_index])
        line_index += 1
        if not node_guid:
            node_guid = re.compile(
                "(?:switch|ca)guid=([a-z0-9]*)").findall(lines[line_index])
            line_index += 1

        if node_guid is None:
            raise Exception(self._lst_file + ":" + str(line_index) +
                            "Bad node format - Missing NodeGuid: " + lines[line_index])

        # Get type
        type_ib = re.compile("(Ca|Switch)").findall(lines[line_index])
        if type_ib is None:
            raise Exception(self._lst_file + ":" + str(line_index) +
                            "Bad node format - Missing type: " + lines[line_index])

        # description - will be parsed according ib type
        desc = []
        # lid - will be parsed according ib type
        lid = []

        # get description an lid according ib type
        if type_ib is not None:
            if type_ib[0] == "Switch":
                type_ib[0] = "SW"
                desc = re.compile("\\# \"([^\\s]+)").findall(lines[line_index])
                lid = re.compile(
                    "Switch.*port 0 lid ([0-9]+)").findall(lines[line_index])
                if lid == []:
                    raise Exception(self._lst_file + ":" + str(line_index) +
                                    ": Bad node format - Missing lid: " + lines[line_index])
                line_index += 1
            else:  # it's CA
                type_ib[0] = "CA"
                desc = re.compile(
                    "(?:\\#\\s\")([^\"]*)").findall(lines[line_index])
                line_index += 1
                lid = re.compile(
                    "(?:\\#\\slid\\s)([0-9a-f]*)").findall(lines[line_index])
                if lid == []:
                    raise Exception(self._lst_file + ":" + str(line_index + 1) +
                                    ": Bad ibnetdiscover output format (Last node): Failed to get first lid: Maybe the port you scan is down")
            # set the lid string to be as hex number of 4 digits format
            if lid[0] == "0":
                raise Exception("-W- Subnet Manager is not active")
            lid[0] = "{0:#0{1}x}".format(int(lid[0]), 6)

        # if parse succeed and pass the validation insert to ib db
        if vend_id != [] or \
                dev_id != [] or \
                node_guid != [] or \
                type_ib != [] or \
                desc != [] or \
                lid != []:

            # check if some of the node parameters valid for db inserting
            if self._is_node_ignored(vend_id[0]) or \
                    dev_id == "43132" or \
                    self._is_ibdr_unsupported(dev_id) or \
                    self._is_node_exist_in_db(node_guid) or \
                    mtcr.isGPUPCIDeviceID(int(dev_id[0], 16)):
                return
            else:
                # create s db row and insert it to the ib db
                ib_node = self._ib_node(
                    node_guid[0], node_guid[0], lid[0], desc[0], type_ib[0], dev_id[0], vend_id[0], dr_pattern)
                self._ib_db.append(ib_node)

    def parse_file(self):
        # flag for the returned value
        active_links_found = False
        dr_pattern = ''
        # check if line has an active link (something to parse) and send (each section) for parsing and inserting to DB
        try:
            with open(self._lst_file, 'r') as f:
                data = f.read()
                dr_list = []
                # split to lines
                lines = data.split("\n")
                for line in lines:
                    pattern = "DR path slid 0; dlid 0; "
                    if pattern in line:
                        dr = line.index(pattern) + len(pattern)
                        dr_right = dr
                        while (line[dr_right] != ' '):
                            dr_right = dr_right + 1

                        dr_pattern = line[dr:dr_right]

                        if ',' in dr_pattern:
                            dr_pattern = dr_pattern.replace(",", ".")

                        dr_list.append(dr_pattern)

                sections = re.split('(?m)^\\n', data)
                dr_counter = 0
                for s in sections:
                    if "vendid" in s:
                        if len(dr_list) > 0:
                            self._add_nodes(
                                s, dr_list[min(dr_counter, len(dr_list) - 1)])
                            dr_counter = dr_counter + 1
                        else:
                            self._add_nodes(s)
                        active_links_found = True

        except Exception as ex:
            print(ex.args[0])
            return active_links_found

        return active_links_found


#######################################################################################################################


class MstIbAdd(object):

    def __init__(self, lst_file,
                 all_or_only_mlnx,
                 file_type,
                 ibdr,
                 hca_id,
                 exit_port_num,
                 print_with_guids=False,
                 aggregated=False):

        if file_type == "diagnet":
            self._parser = _DiagnetParser(lst_file, all_or_only_mlnx, file_type, ibdr, hca_id, exit_port_num,
                                          print_with_guids, aggregated=aggregated)
        else:
            self._parser = _NetParser(lst_file, all_or_only_mlnx, file_type, ibdr, hca_id, exit_port_num,
                                      print_with_guids)

    def ib_add(self):
        """
        Parse the given lst file and return the device names for
        mst inband access as a list
        """
        # parse file (add each ib to db)
        is_active_links = self._parser.parse_file()

        # error message in case file content don't specify an active links
        # if not is_active_links:
        #    sys.stderr.write(
        #        "-W- Non active links found in the IB fabric. These links are not added to the device list. "
        #        "Make sure that a subnet-manager is running.\n")

        return self._parser.retrieve_all_mst_ib()
#######################################################################################################################

# Define a custom argument type for a list of strings


def parse_comma_separated_lids(value):
    try:
        if not isinstance(value, str):
            raise ValueError("Input must be a string.")

        value = value.lower()
        lids = [item.strip() for item in value.split(',') if item.strip()]

        # match "lid-0xZ" while Z represents any hex digit
        lid_pattern = re.compile(r"^lid-0x[0-9a-fA-F]+$")

        invalid_lids = [elem for elem in lids if not lid_pattern.match(elem)]
        if invalid_lids:
            raise ValueError("Invalid lids found: {}".format(invalid_lids))

        unique_lids = {int(lid.replace("lid-", ""), 16) for lid in lids}
        if len(unique_lids) != NUM_OF_LIDS_AGGREGATED:
            raise ValueError("The lids are not unique. Please enter {} unique lids.".format(NUM_OF_LIDS_AGGREGATED))
        return lids

    except ValueError as e:
        print("Error: {}".format(e))
        sys.exit(1)


def main():
    # Create the main parser
    parser = argparse.ArgumentParser(description="A script for IB devices")

    # Create subparsers for different functionalities
    subparsers = parser.add_subparsers(dest="command", help="discovery, aggregated device creation")

    # Subparser for the discovery functionality (requires arguments - .lst file from ibdiagnet etc)
    discovery_parser = subparsers.add_parser('discovery', help='discover the IB devices in the fabric')  # original

    discovery_parser.add_argument('lst_file',
                                  help='<lst_file>')
    discovery_parser.add_argument('all_or_only_mlnx',
                                  choices=['only_mlnx', 'all'],
                                  help='only mellanox or all')
    discovery_parser.add_argument('file_type',
                                  help='file type')
    discovery_parser.add_argument('ibdr',
                                  choices=['0', '1'],
                                  help='ibdr 0|1')
    discovery_parser.add_argument('hca_id',
                                  nargs="?", type=str, default="",
                                  help='hca id')
    discovery_parser.add_argument('exit_port_num',
                                  nargs="?", type=str, default="",
                                  help='exit port number]')
    discovery_parser.add_argument('--with-guids', action='store_true',
                                  default=False, dest='print_with_guids',
                                  help='print with guids')
    discovery_parser.add_argument('aggregated',
                                  default=False,
                                  help='discovery for aggregated port')

    # Subparser for aggregated device creation functionality (does not require arguments)
    aggregated_device_creation_parser = subparsers.add_parser('aggregated_device_creation', help='create an aggregated device by providing the system image guid in hex format (starting with "0x") and the lids constructing the aggregated device.')
    aggregated_device_creation_parser.add_argument("--system_image_guid", type=str, required=True, help='the system image GUID as a hexadecimal number starting with "0x"')
    aggregated_device_creation_parser.add_argument("--lids", nargs="+", type=parse_comma_separated_lids, required=True, help='the lids constructing the aggregrated device. Separated by a comma, without spaces.')
    aggregated_device_creation_parser.add_argument("--hca_id",
                                                   required=False, type=str, default="",
                                                   help='HCA ID (for example mlx5_0)')

    args = parser.parse_args()

    if args.command == "discovery":
        mst_ib_add = MstIbAdd(lst_file=args.lst_file,
                              all_or_only_mlnx=args.all_or_only_mlnx,
                              file_type=args.file_type,
                              ibdr=bool(int(args.ibdr)),
                              hca_id=args.hca_id,
                              exit_port_num=args.exit_port_num,
                              print_with_guids=args.print_with_guids,
                              aggregated=bool(int(args.aggregated)))

        ib_list = mst_ib_add.ib_add()

        for ib in ib_list:
            ib = ib.replace("\"", "")
            print(ib)

    if args.command == "aggregated_device_creation":
        create_aggregated_device(args.system_image_guid, args.lids, args.hca_id)
        exit(0)


if __name__ == '__main__':
    try:
        main()
        sys.exit(0)
    except Exception as e:
        sys.stderr.write("-E- {0}\n".format(str(e)))
        sys.exit(1)
