diff --git a/config/plugins/poe.py b/config/plugins/poe.py new file mode 100644 index 0000000000..0f0bf1ddb4 --- /dev/null +++ b/config/plugins/poe.py @@ -0,0 +1,213 @@ +""" +Config CLI plugin for the SONiC Power over Ethernet feature. +This file is auto-generated by sonic-cli-gen tool but adjusted to meet PoE HLD requirenments. +""" + +import copy +import click +import utilities_common.cli as clicommon +import utilities_common.general as general +from config import config_mgmt + + +# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension. +sonic_cfggen = general.load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen') + + +POE_PORT = 'POE_PORT' + + +def _exit_with_error(*args, **kwargs): + """ Print a message with click.secho and abort CLI. + + Args: + args: Positional arguments to pass to click.secho + kwargs: Keyword arguments to pass to click.secho + """ + + click.secho(*args, **kwargs) + raise click.Abort() + + +def _validate_config_or_raise(cfg): + """ Validate config db data using ConfigMgmt. + + Args: + cfg (Dict): Config DB data to validate. + Raises: + Exception: when cfg does not satisfy YANG schema. + """ + + try: + cfg = sonic_cfggen.FormatConverter.to_serialized(copy.deepcopy(cfg)) + config_mgmt.ConfigMgmt().loadData(cfg) + except Exception as err: + raise Exception('Failed to validate configuration: {}'.format(err)) + + +def _update_entry_validated(db, table, key, data, create_if_not_exists=False): + """ Update entry in table and validate configuration. + If attribute value in data is None, the attribute is deleted. + + Args: + db (swsscommon.ConfigDBConnector): Config DB connector obect. + table (str): Table name to add new entry to. + key (Union[str, Tuple]): Key name in the table. + data (Dict): Entry data. + create_if_not_exists (bool): + In case entry does not exists already a new entry + is not created if this flag is set to False and + creates a new entry if flag is set to True. + Raises: + Exception: when cfg does not satisfy YANG schema. + """ + + cfg = db.get_config() + cfg.setdefault(table, {}) + + if not data: + raise Exception(f"No field/values to update {key}") + + if create_if_not_exists: + cfg[table].setdefault(key, {}) + + if key not in cfg[table]: + raise Exception(f"{key} does not have PoE configuration") + + entry_changed = False + for attr, value in data.items(): + if value == cfg[table][key].get(attr): + continue + entry_changed = True + if value is None: + cfg[table][key].pop(attr, None) + else: + cfg[table][key][attr] = value + + if not entry_changed: + return + + _validate_config_or_raise(cfg) + db.set_entry(table, key, cfg[table][key]) + + +# 'poe' subcommand ("config poe ...") +@click.group( + name="poe", + cls=clicommon.AliasedGroup, +) +def poe(): + """ Configure PoE (Power over Ethernet) feature """ + pass + + +# 'interface' subcommand ("config poe interface ...") +@poe.group( + name="interface", + cls=clicommon.AliasedGroup, +) +def poe_interface(): + """ Configure PoE interface """ + pass + + +# 'status' subcommand ("config poe interface status ...") +@poe_interface.command( + name="status" +) +@click.argument( + "ifname", + nargs=1, + required=True, +) +@click.argument( + "enabled", + nargs=1, + required=True, + type=click.Choice(["enable", "disable"]) +) +@clicommon.pass_db +def poe_intf_status(db, ifname, enabled): + """ Enable or disable PoE on interface """ + + data = {} + if enabled is not None: + data["enabled"] = enabled + + try: + _update_entry_validated(db.cfgdb, POE_PORT, ifname, data) + except Exception as err: + _exit_with_error(f"Error: {err}", fg="red") + + +# 'power-limit' subcommand ("config poe interface power-limit ...") +@poe_interface.command( + name="power-limit" +) +@click.argument( + "ifname", + nargs=1, + required=True, +) +@click.argument( + "power_limit", + nargs=1, + required=True, + type=click.INT +) +@clicommon.pass_db +def poe_intf_power_limit(db, ifname, power_limit): + """ Configure PoE interface power limit """ + + data = {} + if power_limit is not None: + data["pwr_limit"] = power_limit + + try: + _update_entry_validated(db.cfgdb, POE_PORT, ifname, data) + except Exception as err: + _exit_with_error(f"Error: {err}", fg="red") + + +# 'priority' subcommand ("config poe interface priority ...") +@poe_interface.command( + name="priority" +) +@click.argument( + "ifname", + nargs=1, + required=True, +) +@click.argument( + "priority", + nargs=1, + required=True, + type=click.Choice(["low", "high", "crit"]) +) +@clicommon.pass_db +def poe_intf_priority(db, ifname, priority): + """ Configure PoE interface priority """ + + data = {} + if priority is not None: + data["priority"] = priority + + try: + _update_entry_validated(db.cfgdb, POE_PORT, ifname, data) + except Exception as err: + _exit_with_error(f"Error: {err}", fg="red") + + +def register(cli): + """ Register new CLI nodes in root CLI. + + Args: + cli: Root CLI node. + Raises: + Exception: when root CLI already has a command + we are trying to register. + """ + cli_node = poe + if cli_node.name in cli.commands: + raise Exception(f"{cli_node.name} already exists in CLI") + cli.add_command(poe) diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 689ca23b73..48e6bb10fd 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -224,6 +224,9 @@ * [Static DNS show command](#static-dns-show-command) * [Wake-on-LAN Commands](#wake-on-lan-commands) * [Send Wake-on-LAN Magic Packet command](#send-wake-on-lan-magic-packet-command) +* [Power over Ethernet](#power-over-ethernet) + * [PoE show commands](#poe-show-commands) + * [PoE config commands](#poe-config-commands) ## Document History @@ -13701,3 +13704,114 @@ Sending 3 magic packet to 11:33:55:77:99:bb via interface Vlan1000 ``` For the 4th example, it specifise 2 target MAC addresses and `count` is 3. So it'll send 6 magic packets in total. + +## Power over Ethernet + +This section explains all the PoE commands that are supported in SONiC. + +### PoE show commands +This sub-section contains the show commands. + +- Show status of all PoE devices: + ``` + show poe status + ``` + ``` + admin@sonic:~$ show poe status + Id PoE ports Total power Power consump Power available Power limit mode HW info Version + ---- ----------- ------------- --------------- ----------------- ------------------ --------- --------- + 0 16 100.000 W 10.000 W 90.000 W port mcu1 0.1.2.3 + 1 16 100.000 W 10.000 W 90.000 W class mcu2 0.1.2.3 + ``` + +- Show status of all PoE PSEs: + ``` + show poe pse status + ``` + ``` + admin@sonic:~$ show poe pse status + Id Status Temperature SW ver HW ver + ---- -------- ------------- ---------------- ---------------- + 0 active 25.000 C 0.1.2.3 4.5.6.7 + 1 active 25.000 C 0.1.2.3 4.5.6.7 + 2 active 25.000 C 0.1.2.3 4.5.6.7 + 3 active 25.000 C 0.1.2.3 4.5.6.7 + ``` + +- Show status of all PoE interfaces: + ``` + show poe interface status + ``` + ``` + admin@sonic:~$ show poe interface status + Port Status En/Dis Priority Protocol Class A Class B PWR Consump PWR limit Voltage Current + ----------- ---------- -------- ---------- -------------- --------- --------- ------------- ----------- --------- --------- + Ethernet0 delivering enable crit 802.3bt Type 3 2 4 10.000 W 50.000 W 50.000 V 0.200 A + Ethernet1 delivering enable crit 802.3bt Type 3 2 4 10.000 W 50.000 W 50.000 V 0.200 A + Ethernet2 delivering enable low 802.3bt Type 3 2 4 10.000 W 50.000 W 50.000 V 0.200 A + Ethernet3 delivering enable low 802.3bt Type 3 2 4 10.000 W 50.000 W 50.000 V 0.200 A + ``` + +- Show current configuration of all PoE interfaces: + ``` + show poe interface configuration + ``` + ``` + admin@sonic:~$ show poe interface configuration + Port En/Dis Power limit Priority + ----------- -------- ------------- ---------- + Ethernet0 enable 50 crit + Ethernet1 enable 50 crit + Ethernet2 enable 50 low + Ethernet3 enable 50 low + ``` + +### PoE config commands +This sub-section contains the config commands. + + +- Enable PoE: + ``` + admin@sonic:~$ sudo config poe interface status --help + Usage: config poe interface status [OPTIONS] IFNAME [enable|disable] + + Enable or disable PoE on interface + + Options: + -h, -?, --help Show this message and exit. + ``` + ``` + admin@sonic:~$ sudo config poe interface status Ethernet0 enable + ``` + + +- Configure power limit: + ``` + admin@sonic:~$ sudo config poe interface power-limit --help + Usage: config poe interface power-limit [OPTIONS] IFNAME POWER_LIMIT + + Configure PoE interface power limit + + Options: + -h, -?, --help Show this message and exit. + ``` + ``` + admin@sonic:~$ sudo config poe interface power-limit Ethernet0 25 + ``` + + +- Configure priority: + ``` + admin@sonic:~$ sudo config poe interface priority --help + Usage: config poe interface priority [OPTIONS] IFNAME [low|high|crit] + + Configure PoE interface priority + + Options: + -?, -h, --help Show this message and exit. + ``` + ``` + admin@sonic:~$ sudo config poe interface priority Ethernet0 crit + ``` + +Go Back To [Beginning of the document](#) or [Beginning of this section](#power-over-ethernet) diff --git a/show/plugins/poe.py b/show/plugins/poe.py new file mode 100644 index 0000000000..3710c43817 --- /dev/null +++ b/show/plugins/poe.py @@ -0,0 +1,313 @@ +""" +Show CLI plugin for the SONiC Power over Ethernet feature. +This file is auto-generated by sonic-cli-gen tool but adjusted to meet PoE HLD requirenments. +""" + +import click +import tabulate +import natsort +import utilities_common.cli as clicommon + + +ASTERISK = '*' +N_A = 'N/A' +CURRENT = 'current' +POWER = 'power' +VOLTAGE = 'voltage' +TEMPERATURE = 'temperature' + + +def _format_attr_value(entry, attr): + """ Helper that formats attribute to be presented in the table output. + + Args: + entry (Dict[str, str]): CONFIG DB entry configuration. + attr (Dict): Attribute metadata. + + Returns: + str: fomatted attribute value. + """ + + return entry.get(attr["name"], "N/A") + + +def _get_value(value): + return value if value not in [None, ''] else N_A + + +def _format_value(key, value): + units = { + CURRENT: 'A', + POWER: 'W', + VOLTAGE: 'V', + TEMPERATURE: 'C' + } + + formatted_value = _get_value(value) + if formatted_value != N_A and key in units: + return f"{float(formatted_value):.3f} {units[key]}" + return formatted_value + + +@click.group( + name="poe", + cls=clicommon.AliasedGroup +) +def poe(): + """ Show PoE (Power over Ethernet) feature information """ + pass + + +# 'status' subcommand ("show poe status") +@poe.group( + name="status", + cls=clicommon.AliasedGroup, + invoke_without_command=True +) +@clicommon.pass_db +def poe_status(db): + """ Show PoE devices status """ + + state_db = db.db.STATE_DB + delimiter = db.db.get_db_separator(state_db) + poe_dev_keys = db.db.keys(state_db, f"{'POE_DEVICE_TABLE'}{delimiter}{ASTERISK}") + + if poe_dev_keys is None or not poe_dev_keys: + click.echo("PoE devices are not configured") + return + + header = [ + 'Id', + 'PoE ports', + 'Total power', + 'Power consump', + 'Power available', + 'Power limit mode', + 'HW info', + 'Version' + ] + body = [] + + for key in poe_dev_keys: + fv_list = db.db.get_all(state_db, key) + total_pwr = _get_value(fv_list.get('total_pwr')) + pwr_consump = _get_value(fv_list.get('pwr_consump')) + pwr_avail = N_A + if total_pwr is not N_A and pwr_consump is not N_A: + pwr_avail = str(float(total_pwr) - float(pwr_consump)) + row = [ + key.split(delimiter)[1], + _get_value(fv_list.get('total_ports')), + _format_value(POWER, total_pwr), + _format_value(POWER, pwr_consump), + _format_value(POWER, pwr_avail), + _get_value(fv_list.get('pwr_limit_mode')), + _get_value(fv_list.get('hw_info')), + _get_value(fv_list.get('version')), + ] + body.append(row) + + click.echo(tabulate.tabulate(sorted(body), header)) + + +# 'pse' subcommand ("show poe pse ...") +@poe.group( + name="pse", + cls=clicommon.AliasedGroup +) +def pse(): + """ Show PSE (Power Sourcing Equipment) information """ + pass + + +# 'status' subcommand ("show poe pse status") +@pse.group( + name="status", + cls=clicommon.AliasedGroup, + invoke_without_command=True +) +@clicommon.pass_db +def pse_status(db): + """ Show PSE status """ + + state_db = db.db.STATE_DB + delimiter = db.db.get_db_separator(state_db) + pse_keys = db.db.keys(state_db, f"{'POE_PSE_TABLE'}{delimiter}{ASTERISK}") + + if pse_keys is None or not pse_keys: + click.echo("PoE PSE are not configured") + return + + header = [ + 'Id', + 'Status', + 'Temperature', + 'SW ver', + 'HW ver' + ] + + body = [] + + for key in pse_keys: + fv_list = db.db.get_all(state_db, key) + row = [ + key.split(delimiter)[1], + _get_value(fv_list.get('status')), + _format_value(TEMPERATURE, fv_list.get('temperature')), + _get_value(fv_list.get('sw_ver')), + _get_value(fv_list.get('hw_ver')) + ] + body.append(row) + + click.echo(tabulate.tabulate(sorted(body), header)) + + +# 'interface' subcommand ("show poe interface ...") +@poe.group( + name="interface", + cls=clicommon.AliasedGroup +) +def interface(): + """ Show PoE interface information """ + pass + + +# 'configuration' subcommand ("show poe interface configuration") +@interface.group( + name="configuration", + cls=clicommon.AliasedGroup, + invoke_without_command=True +) +@clicommon.pass_db +def configuration(db): + """ Show PoE configuration from Config DB """ + + header = [ + 'Port', + 'En/Dis', + 'Power limit', + 'Priority' + ] + + body = [] + + table = db.cfgdb.get_table('POE_PORT') + for key in natsort.natsorted(table): + entry = table[key] + if not isinstance(key, tuple): + key = (key,) + + row = [*key] + [ + _format_attr_value( + entry, + {'name': 'enabled', + 'description': 'PoE status on port. [enable/disable]' + } + ), + _format_attr_value( + entry, + {'name': 'pwr_limit', + 'description': 'Power limit on PoE port. [0..999]' + } + ), + _format_attr_value( + entry, + {'name': 'priority', + 'description': 'Port priority level. [crit/high/low]' + } + ), + ] + + body.append(row) + + click.echo(tabulate.tabulate(body, header)) + + +# 'status' subcommand ("show poe interface status") +@interface.command() +@click.argument( + "ifname", + required=False +) +@clicommon.pass_db +def status(db, ifname): + """ Show details of the PoE interface """ + + state_db = db.db.STATE_DB + delimiter = db.db.get_db_separator(state_db) + table = 'POE_PORT_TABLE' + + if ifname is None: + poe_port_keys = db.db.keys(state_db, f"{table}{delimiter}{ASTERISK}") + else: + if not ifname.startswith('Ethernet'): + click.echo("Invalid ifname argument") + return + poe_port_keys = db.db.keys(state_db, f"{table}{delimiter}{ifname}") + + if poe_port_keys is None or not poe_port_keys: + if ifname is not None: + click.echo(f"Interface <{ifname}> does not have a PoE configuration") + else: + click.echo("PoE interfaces are not configured") + return + + header = [ + 'Port', + 'Status', + 'En/Dis', + 'Priority', + 'Protocol', + 'Class A', + 'Class B', + 'PWR Consump', + 'PWR limit', + 'Voltage', + 'Current', + ] + + body = [] + + for key in poe_port_keys: + fv_list = db.db.get_all(state_db, key) + row = [ + key.split(delimiter)[1], + _get_value(fv_list.get('status')), + _get_value(fv_list.get('enabled')), + _get_value(fv_list.get('priority')), + _get_value(fv_list.get('protocol')), + _get_value(fv_list.get('class_a')), + _get_value(fv_list.get('class_b')), + _format_value(POWER, fv_list.get('pwr_consump')), + _format_value(POWER, fv_list.get('pwr_limit')), + _format_value(VOLTAGE, fv_list.get('voltage')), + _format_value(CURRENT, fv_list.get('current')), + _get_value(fv_list.get('fp_port')) + ] + body.append(row) + + # sort based on 'fp_port' + try: # in case fp_port is invalid + sorted_body = sorted(body, key=lambda x: int(x[-1]) if x[-1] else -1) + except ValueError: + sorted_body = body + # remove the 'fp_port' + result_body = [sublist[:-1] for sublist in sorted_body] + + click.echo(tabulate.tabulate(result_body, header)) + + +def register(cli): + """ Register new CLI nodes in root CLI. + + Args: + cli (click.core.Command): Root CLI node. + Raises: + Exception: when root CLI already has a command + we are trying to register. + """ + + cli_node = poe + if cli_node.name in cli.commands: + raise Exception(f"{cli_node.name} already exists in CLI") + cli.add_command(poe) diff --git a/tests/poe_input/assert_show_output.py b/tests/poe_input/assert_show_output.py new file mode 100644 index 0000000000..24baad29b9 --- /dev/null +++ b/tests/poe_input/assert_show_output.py @@ -0,0 +1,85 @@ +""" +Module holding the correct values for show CLI command outputs for the poe_test.py +""" + +show_poe_interface_configuration = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 enable 100 high +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + +show_poe_interface_configuration_ethernet0_disable = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 disable 100 high +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + +show_poe_interface_configuration_ethernet0_power_limit_200 = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 enable 200 high +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + + +show_poe_interface_configuration_ethernet0_priority_low = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 enable 100 low +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + +show_poe_interface_configuration_ethernet0_priority_high = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 enable 100 high +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + +show_poe_interface_configuration_ethernet0_priority_crit = """\ +Port En/Dis Power limit Priority +--------- -------- ------------- ---------- +Ethernet0 enable 100 crit +Ethernet4 disable 100 low +Ethernet8 enable 200 crit +""" + +show_poe_interface_status = """\ +Port Status En/Dis Priority Protocol Class A Class B PWR Consump PWR limit \ +Voltage Current +---------- ---------- -------- ---------- ------------ --------- --------- ------------- ----------- \ +--------- --------- +Ethernet0 off disable high IEEE_802.3at 1 1 12.500 W 25.500 W \ +50.000 V 0.250 A +Ethernet4 delivering enable crit IEEE_802.3af 2 2 15.500 W 30.500 W \ +47.123 V 0.329 A +Ethernet8 searching enable low IEEE_802.3at 3 3 8.500 W 30.500 W \ +40.000 V 0.212 A +Ethernet12 fault N/A N/A N/A N/A N/A N/A N/A \ +N/A N/A +""" + +show_poe_pse_status = """\ + Id Status Temperature SW ver HW ver +---- ----------- ------------- -------- -------- + 0 active 40.000 C 1.0.0 1.1.0 + 1 fail 30.000 C 2.0.0 2.2.0 + 2 not present N/A N/A N/A + 3 N/A N/A N/A N/A +""" + +show_poe_status = """\ + Id PoE ports Total power Power consump Power available Power limit mode HW info Version +---- ----------- ------------- --------------- ----------------- ------------------ --------- --------- + 1 10 100.000 W 20.000 W 80.000 W port HW INFO 1 1.0.0 + 2 20 200.000 W 40.000 W 160.000 W class HW INFO 2 2.0.0 + 3 30 300.000 W 60.000 W 240.000 W port HW INFO 3 3.0.0 + 4 40 N/A N/A N/A N/A N/A N/A +""" diff --git a/tests/poe_input/config_db.json b/tests/poe_input/config_db.json new file mode 100644 index 0000000000..4d93c924ac --- /dev/null +++ b/tests/poe_input/config_db.json @@ -0,0 +1,41 @@ +{ + "PORT|Ethernet0": { + "admin_status": "up", + "alias": "etp1", + "index": "0", + "lanes": "0,1,2,3", + "mtu": "9100", + "speed": "100000" + }, + "PORT|Ethernet4": { + "admin_status": "up", + "alias": "etp2", + "index": "1", + "lanes": "4,5,6,7", + "mtu": "9100", + "speed": "100000" + }, + "PORT|Ethernet8": { + "admin_status": "up", + "alias": "etp3", + "index": "2", + "lanes": "8,9,10,11", + "mtu": "9100", + "speed": "100000" + }, + "POE_PORT|Ethernet0": { + "enabled": "enable", + "pwr_limit": "100", + "priority": "high" + }, + "POE_PORT|Ethernet4": { + "enabled": "disable", + "pwr_limit": "100", + "priority": "low" + }, + "POE_PORT|Ethernet8": { + "enabled": "enable", + "pwr_limit": "200", + "priority": "crit" + } +} \ No newline at end of file diff --git a/tests/poe_input/state_db.json b/tests/poe_input/state_db.json new file mode 100644 index 0000000000..af57c43bd1 --- /dev/null +++ b/tests/poe_input/state_db.json @@ -0,0 +1,110 @@ +{ + "POE_PORT_TABLE|Ethernet0": { + "status": "off", + "enabled": "disable", + "priority": "high", + "protocol": "IEEE_802.3at", + "pwr_consump": "12.5", + "pwr_limit": "25.5", + "fp_port": "0", + "class_a": "1", + "class_b": "1", + "current": "0.250", + "voltage": "50.000" + }, + "POE_PORT_TABLE|Ethernet4": { + "status": "delivering", + "enabled": "enable", + "priority": "crit", + "protocol": "IEEE_802.3af", + "pwr_consump": "15.5", + "pwr_limit": "30.5", + "fp_port": "1", + "class_a": "2", + "class_b": "2", + "current": "0.329", + "voltage": "47.123" + }, + "POE_PORT_TABLE|Ethernet8": { + "status": "searching", + "enabled": "enable", + "priority": "low", + "protocol": "IEEE_802.3at", + "pwr_consump": "8.5", + "pwr_limit": "30.5", + "fp_port": "2", + "class_a": "3", + "class_b": "3", + "current": "0.212", + "voltage": "40.000" + }, + "POE_PORT_TABLE|Ethernet12": { + "status": "fault", + "enabled": "", + "priority": "", + "protocol": "", + "pwr_consump": "", + "pwr_limit": "", + "fp_port": "", + "class_a": "", + "class_b": "", + "current": "", + "voltage": "" + }, + "POE_PSE_TABLE|0": { + "status": "active", + "temperature": "40", + "sw_ver": "1.0.0", + "hw_ver": "1.1.0" + }, + "POE_PSE_TABLE|1": { + "status": "fail", + "temperature": "30", + "sw_ver": "2.0.0", + "hw_ver": "2.2.0" + }, + "POE_PSE_TABLE|2": { + "status": "not present", + "temperature": "", + "sw_ver": "", + "hw_ver": "" + }, + "POE_PSE_TABLE|3": { + "status": "", + "temperature": "", + "sw_ver": "", + "hw_ver": "" + }, + "POE_DEVICE_TABLE|1": { + "total_ports": "10", + "total_pwr": "100", + "pwr_consump": "20.0", + "pwr_limit_mode": "port", + "hw_info": "HW INFO 1", + "version": "1.0.0" + }, + "POE_DEVICE_TABLE|2": { + "total_ports": "20", + "total_pwr": "200", + "pwr_consump": "40.0", + "pwr_limit_mode": "class", + "hw_info": "HW INFO 2", + "version": "2.0.0" + }, + "POE_DEVICE_TABLE|3": { + "total_ports": "30", + "total_pwr": "300", + "pwr_consump": "60.0", + "pwr_limit_mode": "port", + "hw_info": "HW INFO 3", + "version": "3.0.0" + }, + "POE_DEVICE_TABLE|4": { + "total_ports": "40", + "total_pwr": "", + "pwr_consump": "", + "pwr_limit_mode": "", + "hw_info": "", + "version": "" + } +} \ No newline at end of file diff --git a/tests/poe_test.py b/tests/poe_test.py new file mode 100644 index 0000000000..ab005d441d --- /dev/null +++ b/tests/poe_test.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +import pytest +import os +import logging +import show.main as show +import config.main as config + +from .poe_input import assert_show_output as valid_output +from utilities_common.db import Db +from click.testing import CliRunner +from .mock_tables import dbconnector + +logger = logging.getLogger(__name__) +test_path = os.path.dirname(os.path.abspath(__file__)) +mock_db_path = os.path.join(test_path, "poe_input") + +SUCCESS = 0 +ERROR = 1 +ERROR2 = 2 + +INVALID_VALUE = 'INVALID' +ETHERNET_0 = 'Ethernet0' +POE = 'poe' +PSE = 'pse' +INTERFACE = 'interface' +STATUS = 'status' +PRIORITY = 'priority' +POWER_LIMIT = 'power-limit' +config_db = 'config_db' +CONFIG_DB = 'CONFIG_DB' +state_db = 'state_db' +STATE_DB = 'STATE_DB' + + +class TestPOE: + @classmethod + def setup_class(cls): + logger.info("SETUP") + os.environ['UTILITIES_UNIT_TESTING'] = "1" + + @classmethod + def teardown_class(cls): + logger.info("TEARDOWN") + os.environ['UTILITIES_UNIT_TESTING'] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + dbconnector.dedicated_dbs[CONFIG_DB] = None + dbconnector.dedicated_dbs[STATE_DB] = None + + @pytest.mark.parametrize("command,value,output", [ + (STATUS, "enable", valid_output.show_poe_interface_configuration), + (STATUS, "disable", valid_output.show_poe_interface_configuration_ethernet0_disable), + (POWER_LIMIT, "200", valid_output.show_poe_interface_configuration_ethernet0_power_limit_200), + (PRIORITY, "low", valid_output.show_poe_interface_configuration_ethernet0_priority_low), + (PRIORITY, "high", valid_output.show_poe_interface_configuration_ethernet0_priority_high), + (PRIORITY, "crit", valid_output.show_poe_interface_configuration_ethernet0_priority_crit), + ]) + def test_config_and_show_poe_interface_success(self, command, value, output): + dbconnector.dedicated_dbs[CONFIG_DB] = os.path.join(mock_db_path, config_db) + db = Db() + runner = CliRunner() + + result = runner.invoke( + config.config.commands[POE].commands[INTERFACE]. + commands[command], [ETHERNET_0, value], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + + result = runner.invoke( + show.cli.commands[POE].commands[INTERFACE]. + commands['configuration'], [], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == output + + @pytest.mark.parametrize("intf,command,value,exit_code", [ + (INVALID_VALUE, STATUS, "enable", ERROR), + (ETHERNET_0, STATUS, INVALID_VALUE, ERROR2), + (ETHERNET_0, POWER_LIMIT, INVALID_VALUE, ERROR2), + (ETHERNET_0, PRIORITY, INVALID_VALUE, ERROR2), + ]) + def test_config_poe_interface_error(self, intf, command, value, exit_code): + dbconnector.dedicated_dbs[CONFIG_DB] = os.path.join(mock_db_path, config_db) + db = Db() + runner = CliRunner() + + result = runner.invoke( + config.config.commands[POE].commands[INTERFACE]. + commands[command], [intf, value], obj=db + ) + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == exit_code + + def test_show_poe_interface_status(self): + dbconnector.dedicated_dbs[STATE_DB] = os.path.join(mock_db_path, state_db) + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands[POE].commands[INTERFACE]. + commands[STATUS], [], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == valid_output.show_poe_interface_status + + def test_show_poe_pse_status(self): + dbconnector.dedicated_dbs[STATE_DB] = os.path.join(mock_db_path, state_db) + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands[POE].commands[PSE]. + commands[STATUS], [], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == valid_output.show_poe_pse_status + + def test_show_poe_status(self): + dbconnector.dedicated_dbs[STATE_DB] = os.path.join(mock_db_path, state_db) + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands[POE].commands[STATUS], [], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == valid_output.show_poe_status