diff --git a/src/north/cli/goldstone/north/cli/portchannel.py b/src/north/cli/goldstone/north/cli/portchannel.py index 5082cd06..e0db5fbf 100644 --- a/src/north/cli/goldstone/north/cli/portchannel.py +++ b/src/north/cli/goldstone/north/cli/portchannel.py @@ -53,7 +53,8 @@ def get_list(session, ds, include_implicit_defaults=True): ) -def add_interfaces(session, id, ifnames): +def add_interfaces(session, id, ifnames, mode): + session.set(f"{pcxpath(id)}/config/mode", mode) prefix = "/goldstone-interfaces:interfaces" for ifname in ifnames: xpath = f"{prefix}/interface[name='{ifname}']" @@ -123,16 +124,22 @@ def show(session, id=None): rows.append( [ item["portchannel-id"], - item["state"]["oper-status"].lower(), - item["state"]["admin-status"].lower(), - ", ".join(natsorted(list(item["state"].get("interface", [])))), + item["state"]["oper-status"].lower() if "state" in item else "-", + item["state"]["admin-status"].lower() + if "state" in item + else item["config"]["admin-status"].lower(), + ", ".join(natsorted(list(item["state"].get("interface", [])))) + if "state" in item + else None, + item["state"]["mode"] + if "state" in item and "mode" in item["state"] + else "None", ] ) - stdout.info( tabulate( rows, - ["Portchannel ID", "oper-status", "admin-status", "Interfaces"], + ["Portchannel ID", "Oper-Status", "Admin-Status", "Interfaces", "Mode"], ) ) @@ -265,19 +272,34 @@ def get_portchannel(conn, ifname): return pc +class PortChannelModeOptions(ConfigCommand): + COMMAND_DICT = {"dynamic": Command, "static": Command} + + +class PortChannelModeCommand(ConfigCommand): + def __init__( + self, context: Context = None, parent: Command = None, name=None, **options + ): + super().__init__(context, parent, name, **options) + if self.root.name != "no": + self.add_command("mode", PortChannelModeOptions) + + class InterfacePortchannelCommand(ConfigCommand): def arguments(self): if self.root.name != "no": - return get_id(self.conn) - return [] + for id in get_id(self.conn): + self.add_command(str(id), PortChannelModeCommand) def exec(self, line): if self.root.name == "no": remove_interfaces(self.conn, self.context.ifnames) else: - if len(line) != 1: - raise InvalidInput(f"usage: {self.name_all()} ") - add_interfaces(self.conn, line[0], self.context.ifnames) + if len(line) != 3 and (line[2] != "dynamic" or line[2] != "static"): + raise InvalidInput( + f"usage: {self.name_all()} mode [dynamic|static]" + ) + add_interfaces(self.conn, line[0], self.context.ifnames, line[2]) @classmethod def to_command(cls, conn, data, **options): diff --git a/src/south/sonic/goldstone/south/sonic/portchannel.py b/src/south/sonic/goldstone/south/sonic/portchannel.py index 7db8dd91..7afbbb90 100644 --- a/src/south/sonic/goldstone/south/sonic/portchannel.py +++ b/src/south/sonic/goldstone/south/sonic/portchannel.py @@ -7,6 +7,12 @@ ) +def _decode(string): + if hasattr(string, "decode"): + return string.decode("utf-8") + return str(string) + + class PortChannelChangeHandler(ChangeHandler): def __init__(self, server, change): super().__init__(server, change) @@ -26,11 +32,22 @@ def __init__(self, server, change): class PortChannelIDHandler(PortChannelChangeHandler): def apply(self, user): if self.type in ["created", "modified"]: - value = self.server.get_default("admin-status") + self.mode = self.change.value + admin = self.server.get_default("admin-status") + mtu = value = self.server.get_default("mtu") + if self.mode == "static": + self.server.sonic.set_config_db( + self.pid, "static", "true", "PORTCHANNEL" + ) + self.server.sonic.set_config_db(self.pid, "mode", self.mode, "PORTMODE") self.server.sonic.set_config_db( - self.pid, "admin-status", value, "PORTCHANNEL" + self.pid, "admin-status", admin, "PORTCHANNEL" ) + self.server.sonic.set_config_db(self.pid, "mtu", mtu, "PORTCHANNEL") else: + self.server.sonic.sonic_db.delete( + self.server.sonic.sonic_db.CONFIG_DB, f"PORTMODE|{self.pid}" + ) self.server.sonic.sonic_db.delete( self.server.sonic.sonic_db.CONFIG_DB, f"PORTCHANNEL|{self.pid}" ) @@ -38,12 +55,20 @@ def apply(self, user): class AdminStatusHandler(PortChannelChangeHandler): def apply(self, user): + self.mode = _decode( + self.server.sonic.sonic_db.get( + self.server.sonic.sonic_db.CONFIG_DB, f"PORTMODE|{self.pid}", "mode" + ) + ) if self.type in ["created", "modified"]: value = self.change.value else: value = self.server.get_default("admin-status") logger.debug(f"set {self.pid}'s admin-status to {value}") - self.server.sonic.set_config_db(self.pid, "admin-status", value, "PORTCHANNEL") + if self.type not in ["deleted"] and self.mode in ["dynamic", "static"]: + self.server.sonic.set_config_db( + self.pid, "admin-status", value, "PORTCHANNEL" + ) def revert(self, user): # TODO @@ -52,12 +77,18 @@ def revert(self, user): class MTUHandler(PortChannelChangeHandler): def apply(self, user): + self.mode = _decode( + self.server.sonic.sonic_db.get( + self.server.sonic.sonic_db.CONFIG_DB, f"PORTMODE|{self.pid}", "mode" + ) + ) if self.type in ["created", "modified"]: value = self.change.value else: value = self.server.get_default("mtu") logger.debug(f"set {self.pid}'s mtu to {value}") - self.server.sonic.set_config_db(self.pid, "mtu", value, "PORTCHANNEL") + if self.type not in ["deleted"] and self.mode in ["dynamic", "static"]: + self.server.sonic.set_config_db(self.pid, "mtu", value, "PORTCHANNEL") class InterfaceHandler(PortChannelChangeHandler): @@ -71,6 +102,9 @@ def validate(self, user): def apply(self, user): if self.type in ["created", "modified"]: ifname = self.xpath[-1][2][0][1] + self.server.sonic.set_config_db( + self.pid, "admin-status", "up", "PORTCHANNEL" + ) self.server.sonic.sonic_db.set( self.server.sonic.sonic_db.CONFIG_DB, f"PORTCHANNEL_MEMBER|{self.pid}|{ifname}", @@ -94,7 +128,8 @@ def __init__(self, conn, sonic): "portchannel-group": { "portchannel-id": NoOp, "config": { - "portchannel-id": PortChannelIDHandler, + "portchannel-id": NoOp, + "mode": PortChannelIDHandler, "admin-status": AdminStatusHandler, "mtu": MTUHandler, "interface": InterfaceHandler, @@ -134,6 +169,15 @@ def oper_cb(self, xpath, priv): members = self.sonic.get_keys(f"LAG_MEMBER_TABLE:{name}:*", "APPL_DB") members = [m.split(":")[-1] for m in members] state["interface"] = members + state["interface"] = members + static = self.sonic.sonic_db.get( + self.sonic.sonic_db.CONFIG_DB, f"PORTCHANNEL|{name}", "static" + ) + if static == "true": + state["mode"] = "static" + else: + state["mode"] = "dynamic" + r.append({"portchannel-id": name, "state": state}) logger.debug(f"portchannel: {r}") @@ -165,10 +209,15 @@ async def reconcile(self): ) for pc in pc_list: pid = pc["portchannel-id"] - for leaf in ["admin-status", "mtu"]: - default = self.get_default(leaf) - value = pc["config"].get(leaf, default) - self.sonic.set_config_db(pid, leaf, value, "PORTCHANNEL") + mode = pc["config"].get("mode", "") + if mode in ["dynamic", "static"]: + for leaf in ["admin-status", "mtu"]: + default = self.get_default(leaf) + value = pc["config"].get(leaf, default) + self.sonic.set_config_db(pid, "mode", mode, "PORTMODE") + if mode == "static": + self.sonic.set_config_db(pid, "static", "true", "PORTCHANNEL") + self.sonic.set_config_db(pid, leaf, value, "PORTCHANNEL") for intf in pc["config"].get("interface", []): self.sonic.set_config_db( pid + "|" + intf, "NULL", "NULL", "PORTCHANNEL_MEMBER" diff --git a/yang/goldstone-portchannel.yang b/yang/goldstone-portchannel.yang index 63b395b5..db235f3b 100644 --- a/yang/goldstone-portchannel.yang +++ b/yang/goldstone-portchannel.yang @@ -19,7 +19,7 @@ module goldstone-portchannel { description "Goldstone portchannel"; - revision 2021-05-30 { + revision 2023-07-10 { description "Initial revision."; } @@ -49,6 +49,14 @@ module goldstone-portchannel { default 9100; } + leaf mode { + type enumeration{ + enum dynamic; + enum static; + } + description "portchannel mode"; + } + uses gs-if:interface-common-config; leaf-list interface {