Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gearsyncd/gearboxparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ bool GearboxParser::parse()
attr = std::make_pair("macsec_ipg", std::to_string(val.get<int>()));
attrs.push_back(attr);
}
if (phy.find("macsec_supported") != phy.end())
{
val = phy["macsec_supported"];
attr = std::make_pair("macsec_supported", val.get<bool>() ? "true" : "false");
attrs.push_back(attr);
}
if (phy.find("hwinfo") == phy.end())
{
SWSS_LOG_ERROR("missing 'hwinfo' field in 'phys' item %d in gearbox configuration", iter);
Expand Down
7 changes: 7 additions & 0 deletions lib/gearboxutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ std::map<int, gearbox_phy_t> GearboxUtils::loadPhyMap(Table *gearboxTable)
{
gearbox_phy_t phy = {};

// default capability if absent
phy.macsec_supported = true;

gearboxTable->get(k, ovalues);
for (auto &val : ovalues)
{
Expand Down Expand Up @@ -193,6 +196,10 @@ std::map<int, gearbox_phy_t> GearboxUtils::loadPhyMap(Table *gearboxTable)
{
phy.macsec_ipg = std::stoi(val.second);
}
else if (val.first == "macsec_supported")
{
phy.macsec_supported = (val.second == "true");
}
}
gearboxPhyMap[phy.phy_id] = phy;
}
Expand Down
1 change: 1 addition & 0 deletions lib/gearboxutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ typedef struct
uint32_t bus_id;
uint32_t context_id;
uint32_t macsec_ipg;
bool macsec_supported;
} gearbox_phy_t;

typedef struct
Expand Down
42 changes: 37 additions & 5 deletions orchagent/macsecorch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,25 @@ class MACsecOrchContext
{
return nullptr;
}
if (port->m_line_side_id != SAI_NULL_OBJECT_ID)

const auto *phy = get_gearbox_phy();
bool force_npu = true;
if (phy)
{
force_npu = !phy->macsec_supported;
}

if (!force_npu && port->m_line_side_id != SAI_NULL_OBJECT_ID)
{
m_port_id = std::make_unique<sai_object_id_t>(port->m_line_side_id);
}
else
{
if (force_npu && port->m_line_side_id != SAI_NULL_OBJECT_ID)
{
SWSS_LOG_NOTICE("MACsec: %s -> backend=NPU (phy marked unsupported), using NPU port",
m_port_name ? m_port_name->c_str() : "");
}
m_port_id = std::make_unique<sai_object_id_t>(port->m_port_id);
}
}
Expand All @@ -379,12 +392,27 @@ class MACsecOrchContext
{
switchId = port->m_switch_id;
}

if (switchId == SAI_NULL_OBJECT_ID)
{
SWSS_LOG_ERROR("Switch ID cannot be found");
return nullptr;
}
m_switch_id = std::make_unique<sai_object_id_t>(switchId);

const auto *phy = port ? m_orch->m_port_orch->getGearboxPhy(*port) : nullptr;
bool force_npu = true;
if (phy)
{
force_npu = !phy->macsec_supported;
}

if (force_npu && (*m_switch_id != gSwitchId))
{
SWSS_LOG_NOTICE("MACsec: %s -> backend=NPU (phy marked unsupported), override switch to global",
m_port_name ? m_port_name->c_str() : "");
*m_switch_id = gSwitchId;
}
}
return m_switch_id.get();
}
Expand Down Expand Up @@ -2322,28 +2350,32 @@ bool MACsecOrch::deleteMACsecSA(sai_object_id_t sa_id)

FlexCounterManager& MACsecOrch::MACsecSaStatManager(MACsecOrchContext &ctx)
{
if (ctx.get_gearbox_phy() != nullptr)
const auto *phy = ctx.get_gearbox_phy();
if (phy && phy->macsec_supported)
return m_gb_macsec_sa_stat_manager;
return m_macsec_sa_stat_manager;
}

FlexCounterManager& MACsecOrch::MACsecSaAttrStatManager(MACsecOrchContext &ctx)
{
if (ctx.get_gearbox_phy() != nullptr)
const auto *phy = ctx.get_gearbox_phy();
if (phy && phy->macsec_supported)
return m_gb_macsec_sa_attr_manager;
return m_macsec_sa_attr_manager;
}

FlexCounterManager& MACsecOrch::MACsecFlowStatManager(MACsecOrchContext &ctx)
{
if (ctx.get_gearbox_phy() != nullptr)
const auto *phy = ctx.get_gearbox_phy();
if (phy && phy->macsec_supported)
return m_gb_macsec_flow_stat_manager;
return m_macsec_flow_stat_manager;
}

Table& MACsecOrch::MACsecCountersMap(MACsecOrchContext &ctx)
{
if (ctx.get_gearbox_phy() != nullptr)
const auto *phy = ctx.get_gearbox_phy();
if (phy && phy->macsec_supported)
return m_gb_macsec_counters_map;
return m_macsec_counters_map;
}
Expand Down
105 changes: 105 additions & 0 deletions tests/gearbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Generic helper functions for gearbox testing.

This module provides reusable utility functions for gearbox-related tests,
including port management and configuration setup.
"""

import json


class TestGearboxHelper:
"""Helper class for gearbox-related test operations."""

@staticmethod
def get_first_gearbox_port(gearbox):
"""
Get the first port from Gearbox object (reads from _GEARBOX_TABLE in APPL_DB).

Args:
gearbox: Gearbox fixture

Returns:
tuple: (port_name, phy_id) - First available gearbox port and its PHY ID
"""
assert len(gearbox.interfaces) > 0, "No interfaces found in gearbox"

# Get first interface
first_idx = next(iter(gearbox.interfaces))
first_intf = gearbox.interfaces[first_idx]

port_name = first_intf.get("name")
phy_id = first_intf.get("phy_id")

assert port_name, "First interface has no 'name' field"
assert phy_id is not None, "First interface has no 'phy_id' field"

return port_name, phy_id

@staticmethod
def configure_gearbox_macsec_support(dvs, gearbox, phy_id=None, macsec_supported=None):
"""
Configure MACsec support on a gearbox PHY by modifying gearbox_config.json and restarting DVS.

This is necessary because:
1. gearsyncd reads gearbox_config.json only at startup
2. PortsOrch caches _GEARBOX_TABLE only at startup (initGearbox)
3. MACsecOrch reads from PortsOrch's cache, not from _GEARBOX_TABLE
4. Full DVS restart is the only reliable way to reload the configuration
because partial service restarts cause inconsistent port state

Args:
dvs: Docker Virtual Switch instance
gearbox: Gearbox fixture
phy_id: PHY ID (string, e.g., "1"). If None, uses the first PHY from Gearbox object.
macsec_supported: None (remove field), True, or False
"""
# If phy_id not provided, use the first PHY from Gearbox object
if phy_id is None:
assert len(gearbox.phys) > 0, "No PHYs found in gearbox"
phy_id = next(iter(gearbox.phys))
print(f"No phy_id provided, using first PHY: {phy_id}")

# Resolve symlink to get actual config path
config_path = "/usr/share/sonic/hwsku/gearbox_config.json"
rc, actual_path = dvs.runcmd(f"readlink -f {config_path}")
if rc == 0 and actual_path.strip():
config_path = actual_path.strip()

# Read current config
rc, config_json = dvs.runcmd(f"cat {config_path}")
assert rc == 0, f"Failed to read gearbox_config.json from {config_path}"
config = json.loads(config_json)

phy_id = int(phy_id)

# Find and modify the PHY configuration
phy_found = False
for phy in config.get("phys", []):
if phy.get("phy_id") == phy_id:
phy_found = True
if macsec_supported is None:
# Remove the field if it exists
if "macsec_supported" in phy:
del phy["macsec_supported"]
else:
# Set the field
phy["macsec_supported"] = macsec_supported
break

assert phy_found, f"PHY {phy_id} not found in gearbox_config.json"

# Write modified config back using heredoc
config_str = json.dumps(config, indent=2)
heredoc = "__GEARBOX_JSON__"
rc, _ = dvs.runcmd(
"bash -lc 'cat > {path} <<\"{tag}\"\n{payload}\n{tag}\n'".format(
path=config_path,
tag=heredoc,
payload=config_str,
)
)
assert rc == 0, f"Failed to write modified config to {config_path}"

# Restart DVS to reload configuration
dvs.restart()
Loading
Loading