diff --git a/config/transformer/models_list b/config/transformer/models_list index 133a3bfa3..0251a2fed 100644 --- a/config/transformer/models_list +++ b/config/transformer/models_list @@ -1,4 +1,6 @@ #List yang models transformer need to load +ietf-interfaces.yang +iana-if-type.yang openconfig-acl.yang openconfig-acl-annot.yang openconfig-sampling-sflow.yang diff --git a/models/yang/annotations/openconfig-interfaces-annot.yang b/models/yang/annotations/openconfig-interfaces-annot.yang index 4eeee0a3b..62a55f758 100644 --- a/models/yang/annotations/openconfig-interfaces-annot.yang +++ b/models/yang/annotations/openconfig-interfaces-annot.yang @@ -63,6 +63,60 @@ module openconfig-interfaces-annot { } } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:type { + deviate add { + sonic-ext:field-transformer "intf_type_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-intf:type { + deviate add { + sonic-ext:field-transformer "intf_type_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:description { + deviate add { + sonic-ext:field-transformer "intf_description_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:ifindex { + deviate add { + sonic-ext:field-transformer "intf_ifindex_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:oper-status { + deviate add { + sonic-ext:field-transformer "intf_oper_status_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:last-change { + deviate add { + sonic-ext:field-transformer "intf_last_change_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:management { + deviate add { + sonic-ext:field-transformer "intf_mgmt_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:cpu { + deviate add { + sonic-ext:field-transformer "intf_cpu_xfmr"; + } + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:logical { + deviate add { + sonic-ext:field-transformer "intf_logical_xfmr"; + } + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-intf:mtu { deviate add { sonic-ext:field-transformer "intf_mtu_xfmr"; @@ -219,6 +273,12 @@ module openconfig-interfaces-annot { } } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:state { + deviate add { + sonic-ext:db-name "APPL_DB"; + } + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:state/oc-ip:enabled { deviate add { sonic-ext:field-transformer "ipv6_enabled_xfmr"; diff --git a/models/yang/common/openconfig-if-8021x.yang b/models/yang/common/openconfig-if-8021x.yang new file mode 100644 index 000000000..29a5474c8 --- /dev/null +++ b/models/yang/common/openconfig-if-8021x.yang @@ -0,0 +1,318 @@ +module openconfig-if-8021x { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/8021x"; + + prefix "oc-1x"; + + // import some basic types + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-vlan { prefix oc-vlan; } + import openconfig-vlan-types { prefix oc-vlan-types; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing 8021X. Augments the OpenConfig models for + wired interfaces and wireless SSIDs for configuration and state."; + + oc-ext:openconfig-version "0.0.1"; + + revision "2020-01-28" { + description + "Initial draft of model, including only the most common 802.1X + configuration and state use-cases."; + reference "0.0.1"; + } + + // grouping statements + + grouping vlan-map-config { + description + "Configuration data for mapping from VLAN name to VLAN id."; + + leaf vlan-name { + type string; + mandatory true; + description + "The VLAN name to be mapped to the VLAN id."; + } + + leaf id { + type oc-vlan-types:vlan-id; + mandatory true; + description + "The VLAN id to be mapped to the VLAN name."; + } + } + + grouping dot1x-port-config { + description + "802.1X port-based configuration."; + + leaf authenticate-port { + type boolean; + description + "Enable 802.1X port control on an interface."; + } + + leaf host-mode { + type enumeration { + enum SINGLE_HOST { + description + "Only single supplicant can communicate through the port. + If the supplicant logs off or the port state is changed, + the port becomes unauthenticated."; + } + enum MULTI_HOST { + description + "Multiple hosts can communicate over a single port. + Only the first supplicant is authenticated while + subsequent hosts have network access without having to + authenticate."; + } + enum MULTI_DOMAIN { + description + "Allows for authentication of multiple clients + individually on one authenticator port."; + } + } + description + "Allow for single or multiple hosts to communicate through + an 802.1X controlled port."; + } + + leaf reauthenticate-interval { + type uint16; + units seconds; + description + "Enable periodic re-authentication of the device connected + to this port. Setting a value of 0 disabled reauthentication + on this port."; + } + + leaf retransmit-interval { + type uint16; + units seconds; + description + "How long the interface waits for a response from an + EAPoL Start before restarting 802.1X authentication on the + port."; + } + + leaf supplicant-timeout { + type uint16; + units seconds; + description + "Time to wait for a response from the supplicant before + restarting the 802.1X authentication process."; + } + + leaf max-requests { + type uint16; + description + "Maximum number of times an EAPoL request packet is retransmitted + to the supplicant before the authentication session fails."; + } + + leaf server-fail-vlan { + type union { + type string; + type oc-vlan-types:vlan-id; + } + description + "If RADIUS is unresponsive, the supplicant shall be placed in + this VLAN. If this VLAN is configured as a VLAN name, the + vlan-map must be populated for the Authenticator to map this + VLAN name to a VLAN id."; + } + + leaf auth-fail-vlan { + type union { + type string; + type oc-vlan-types:vlan-id; + } + description + "Upon failure to authenticate, the port is set to this VLAN. + If this VLAN is a configured as a VLAN name, the vlan-map must + be populated for the Authenticator to map this VLAN name to a + VLAN id."; + } + } + + grouping vlan-map-top { + description + "Top-level grouping for vlan-map configuration and Operational + state data."; + + container dot1x-vlan-map { + description + "Enclosing container for mapping a VLAN name to VLAN id"; + + list vlan-name { + key "vlan-name"; + description + "A list of mappings from VLAN name to VLAN id. + Entries in this list are utilized for DVA using a VLAN + name; eg when RADIUS returns a VLAN name as the + tunnel-private-group-id."; + reference + "RFC 2868: RADIUS Attributes for Tunnel Protocol Support"; + + leaf vlan-name { + type leafref { + path "../config/vlan-name"; + } + description "References the configured VLAN name"; + } + + container config { + description "Configuration data for each configured VLAN + name in the VLAN ID to VLAN name mapping"; + + uses vlan-map-config; + } + + container state { + config false; + description + "Operational state data for each VLAN id + to VLAN name mapping."; + + uses vlan-map-config; + } + } + } + } + + grouping dot1x-sessions-top { + description + "Top-level grouping for 802.1X sessions."; + container authenticated-sessions { + description + "Top level container for authenticated sessions state data."; + + list authenticated-session { + key "mac"; + config false; + description + "The list of authenticated sessions on this device."; + + leaf mac { + type leafref { + path "../state/mac"; + } + description + "Device MAC address."; + } + + container state { + config false; + description + "Top level state container for 802.1X."; + + leaf mac { + type oc-yang:mac-address; + description + "Device MAC address."; + } + uses dot1x-sessions-state; + } + } + } + } + + grouping dot1x-sessions-state { + description + "Grouping for 802.1X sessions State data."; + + leaf session-id { + type string; + description + "The locally-significant session id which this authenticated + session applies to. Typically used for RADIUS accounting or + other system level telemetry."; + } + + leaf status { + type enumeration { + enum AUTHENTICATED { + description + "The session has succesfully completed one of the authentication + methods allowed on the port."; + } + enum AUTHENTICATING { + description + "The session is in the process of authenticating."; + } + enum FAILED_AUTHENTICATION { + description + "An authentication has been attempted for this session, + and has failed."; + } + enum SUPPLICANT_TIMEOUT { + description + "An authentication has been attempted for this session, + however the supplicant has not responded. This is likely + due to the attached devices lack of 802.1X support."; + } + } + description + "The status of the 802.1X session for a device."; + } + } + + grouping dot1x-top { + description + "Top-level grouping for 802.1X configuration and operational + state data."; + + container dot1x { + description + "Top level container for 802.1X configuration and + state data."; + + container config { + description + "Top level configuration container for 802.1X."; + + uses dot1x-port-config; + } + + container state { + config false; + description + "Top level state container for 802.1X."; + + uses dot1x-port-config; + } + } + uses dot1x-sessions-top; + } + + // Augment statements + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet" { + description + "Adds 802.1X settings to individual Ethernet interfaces"; + + uses dot1x-top; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-vlan:switched-vlan" { + description + "Adds vlan-map to switched-vlans."; + + uses vlan-map-top; + } +} diff --git a/models/yang/common/openconfig-if-rates.yang b/models/yang/common/openconfig-if-rates.yang new file mode 100644 index 000000000..78d972e14 --- /dev/null +++ b/models/yang/common/openconfig-if-rates.yang @@ -0,0 +1,114 @@ +module openconfig-if-rates { + + yang-version "1"; + + namespace "http://openconfig.net/yang/interfaces/rates"; + + prefix "oc-if-rates"; + + import openconfig-extensions { prefix oc-ext; } + import openconfig-interfaces { prefix "oc-if"; } + + organization + "OpenConfig working group"; + + contact + "www.openconfig.net"; + + description + "This module adds configuration and operational state for interface rates."; + + oc-ext:openconfig-version "0.1.0"; + + revision 2024-04-02 { + description + "Augment of /interfaces/interface to include interface rates."; + reference + "0.1.0"; + } + + grouping interface-rates-config { + description + "Grouping of interface rates related configuration"; + + leaf load-interval { + type uint16 { + range "1..600"; + } + units "seconds"; + default 300; + description + "The interval of interface rates calculation in seconds"; + } + } + + grouping interface-rates-state { + description + "Grouping of interface rates with different direction and units"; + + leaf load-interval { + type uint16; + units "seconds"; + description + "The interval of interface rates calculation in seconds"; + } + + leaf out-bits-rate { + type uint64; + units "bps"; + description + "The calculated transmitted rate of the interface, measured in bits + per second."; + } + + leaf in-bits-rate { + type uint64; + units "bps"; + description + "The calculated received rate of the interface, measured in bits + per second."; + } + + leaf out-pkts-rate { + type uint64; + units "pps"; + description + "The calculated transmitted rate of the interface, measured in packets + per second."; + } + + leaf in-pkts-rate { + type uint64; + units "pps"; + description + "The calculated received rate of the interface, measured in packets + per second."; + } + } + + augment "/oc-if:interfaces/oc-if:interface" { + description + "Adds interface rates."; + + container rates { + description + "Enclosing container for interface rates."; + + container config { + description + "Enclosing container for interface rates related configuration"; + + uses interface-rates-config; + } + + container state { + config false; + description + "Enclosing container for operational state representing + interface rates."; + + uses interface-rates-state; + } + } + } +} diff --git a/models/yang/common/openconfig-if-sdn-ext.yang b/models/yang/common/openconfig-if-sdn-ext.yang new file mode 100644 index 000000000..a96e1b823 --- /dev/null +++ b/models/yang/common/openconfig-if-sdn-ext.yang @@ -0,0 +1,106 @@ +module openconfig-if-sdn-ext { + yang-version "1"; + + namespace "http://openconfig.net/interfaces/sdn-ext"; + prefix "oc-if-sdn"; + + import openconfig-extensions { prefix oc-ext; } + import openconfig-interfaces { prefix oc-if; } + + organization + "OpenConfig working group"; + + contact + "www.openconfig.net"; + + description + "This module provides extensions to the OpenConfig interfaces + module for network elements that support external 'SDN' control + of their interfaces."; + + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + oc-ext:openconfig-version "0.2.0"; + + revision 2024-02-21 { + description + "Initial revision."; + reference "0.2.0"; + } + + revision 2021-03-30 { + description + "Initial revision."; + reference "0.1.0"; + } + + grouping sdn-interface-config { + description + "Configuration parameters applicable to interfaces on devices + that support SDN control."; + + leaf forwarding-viable { + type boolean; + default true; + description + "This value indicates whether the interface may be used + to route traffic or not. If set to false, the + interface is not used for forwarding traffic, but as long as + it is up, the interface still maintains its layer-2 + adjacencies and runs its configured layer-2 functions + (e.g., LLDP, etc.). + This is used by an external programming entity to disable an interface + (usually part of an aggregate) for the purposes of forwarding + traffic. This allows a logical aggregate to continue to be + used with partial capacity. Setting `forwarding-viable = false` is not + equivalent to administratively disabling the interface. + Some rules to follow when an interface or aggregate interface is set for + Forwarding-viable=False: + 1. Aggregate interface '/interfaces/interface/aggregation/state/min-links' + checks should be evaluated based on + `/interfaces/interface/state/oper-status`. 'min-links' should not be + affected by the use of forwarding viable. + + 2. L2 protocols like LLDP and LACP must be processed normally on + transmit and receive on such ports/bundles. IS-IS PDUs should be + handled as per the requirements for L3 packets below. + + 3. L3 packets must not be transmitted on the interface. + + 4. Received L3 packets must be processed normally. Received data-plane + traffic will continue to forwarded to its destination post FIB lookup. + Received control-plane traffic must also be processed normally. + + 5. It is possible that the dead-interval or hold-down timer of L3 + protocols like IS-IS/BGP on the peer router may expire taking down the + adjacency or peering on that connection. However, the peer may still + continue to transmit packets which are received by the local device. + These received packet should continue to be processed normally as + per rule #4 above. + + For example, if the peer's forwarding table is programmed using gRIBI + by an external controller, the local device will continue to receive + packets. + + 6. An implementation should follow rule #3 even when the subject + interface on the local device is the last resort of communication for a + given destination. For example, the only nexthop for a destination is + an aggregate interface which has all member interfaces set to + forwarding-viable = false. In this scenario all L3 packets for that + destination will be dropped."; + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:config" { + description + "Add SDN extensions to interface intended configuration."; + uses sdn-interface-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:state" { + description + "Add SDN extensions to interface applied configuration."; + uses sdn-interface-config; + } +} diff --git a/models/yang/common/openconfig-if-types.yang b/models/yang/common/openconfig-if-types.yang deleted file mode 100644 index 27d2dc1d8..000000000 --- a/models/yang/common/openconfig-if-types.yang +++ /dev/null @@ -1,108 +0,0 @@ -module openconfig-if-types { - yang-version "1"; - - namespace "http://openconfig.net/yang/openconfig-if-types"; - - prefix "oc-ift"; - - // import statements - import openconfig-extensions { prefix oc-ext; } - - // meta - organization - "OpenConfig working group"; - - contact - "OpenConfig working group - netopenconfig@googlegroups.com"; - - description - "This module contains a set of interface type definitions that - are used across OpenConfig models. These are generally physical - or logical interfaces, distinct from hardware ports (which are - described by the OpenConfig platform model)."; - - oc-ext:openconfig-version "0.2.1"; - - revision "2018-11-21" { - description - "Add OpenConfig module metadata extensions."; - reference "0.2.1"; - } - - revision "2018-01-05" { - description - "Add tunnel types into the INTERFACE_TYPE identity."; - reference "0.2.0"; - } - - revision "2016-11-14" { - description - "Initial version"; - reference "0.1.0"; - } - - // OpenConfig specific extensions for module metadata. - oc-ext:regexp-posix; - oc-ext:catalog-organization "openconfig"; - oc-ext:origin "openconfig"; - - identity INTERFACE_TYPE { - description - "Base identity from which interface types are derived."; - } - - identity IF_ETHERNET { - base INTERFACE_TYPE; - description - "Ethernet interfaces based on IEEE 802.3 standards, as well - as FlexEthernet"; - reference - "IEEE 802.3-2015 - IEEE Standard for Ethernet - OIF Flex Ethernet Implementation Agreement 1.0"; - } - - identity IF_AGGREGATE { - base INTERFACE_TYPE; - description - "An aggregated, or bonded, interface forming a - Link Aggregation Group (LAG), or bundle, most often based on - the IEEE 802.1AX (or 802.3ad) standard."; - reference - "IEEE 802.1AX-2008"; - } - - identity IF_LOOPBACK { - base INTERFACE_TYPE; - description - "A virtual interface designated as a loopback used for - various management and operations tasks."; - } - - identity IF_ROUTED_VLAN { - base INTERFACE_TYPE; - description - "A logical interface used for routing services on a VLAN. - Such interfaces are also known as switch virtual interfaces - (SVI) or integrated routing and bridging interfaces (IRBs)."; - } - - identity IF_SONET { - base INTERFACE_TYPE; - description - "SONET/SDH interface"; - } - - identity IF_TUNNEL_GRE4 { - base INTERFACE_TYPE; - description - "A GRE tunnel over IPv4 transport."; - } - - identity IF_TUNNEL_GRE6 { - base INTERFACE_TYPE; - description - "A GRE tunnel over IPv6 transport."; - } - -} diff --git a/models/yang/common/openconfig-inet-types.yang b/models/yang/common/openconfig-inet-types.yang index 7c23d2b38..ca779700e 100644 --- a/models/yang/common/openconfig-inet-types.yang +++ b/models/yang/common/openconfig-inet-types.yang @@ -224,6 +224,26 @@ module openconfig-inet-types { RFC 4001: Textual Conventions for Internet Network Addresses"; } + typedef ipv6-address-type { + type enumeration { + enum GLOBAL_UNICAST { + description + "The IPv6 address is a global unicast address type and must be in + the format defined in RFC 4291 section 2.4."; + } + enum LINK_LOCAL_UNICAST { + description + "The IPv6 address is a Link-Local unicast address type and must be + in the format defined in RFC 4291 section 2.4."; + } + } + description + "The value represents the type of IPv6 address"; + reference + "RFC 4291: IP Version 6 Addressing Architecture + section 2.5"; + } + typedef domain-name { type string { length "1..253"; diff --git a/models/yang/extensions/openconfig-interfaces-deviation.yang b/models/yang/extensions/openconfig-interfaces-deviation.yang index 097100f66..3c19f77a2 100644 --- a/models/yang/extensions/openconfig-interfaces-deviation.yang +++ b/models/yang/extensions/openconfig-interfaces-deviation.yang @@ -15,6 +15,11 @@ module openconfig-interfaces-deviation { import openconfig-if-ethernet {prefix oc-eth; } import openconfig-if-aggregate {prefix oc-lag; } import openconfig-if-tunnel {prefix oc-tun; } + import openconfig-if-rates {prefix oc-if-rates; } + import openconfig-if-8021x {prefix oc-1x; } + import openconfig-if-poe {prefix oc-poe; } + import openconfig-if-sdn-ext {prefix oc-if-sdn; } + import openconfig-if-ip-ext {prefix oc-ip-ext; } organization "SONiC"; @@ -35,43 +40,27 @@ module openconfig-interfaces-deviation { deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-intf:loopback-mode { deviate not-supported; - } + } deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-vlan:tpid { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-intf:type { + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:config/oc-if-sdn:forwarding-viable { deviate not-supported; - } + } deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:loopback-mode { deviate not-supported; - } - - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:oper-status { - deviate not-supported; - } - - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:last-change { - deviate not-supported; - } - - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:type { - deviate not-supported; - } - - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:ifindex { - deviate not-supported; - } + } - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:logical { + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-vlan:tpid { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-vlan:tpid { + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-if-sdn:forwarding-viable { deviate not-supported; - } + } deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:counters/oc-intf:in-unknown-protos { deviate not-supported; @@ -89,10 +78,38 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:counters/oc-intf:resets { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:counters/oc-intf:interface-transitions { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:state/oc-intf:counters/oc-intf:link-transitions { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:hold-time { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:config/oc-intf:description { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:config/oc-intf:enabled { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:description { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:enabled { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:ifindex { deviate not-supported; } @@ -117,6 +134,14 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:management { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:cpu { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-intf:state/oc-intf:counters { deviate not-supported; } @@ -125,6 +150,14 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv4/oc-ip:addresses/oc-ip:address/oc-ip:config/oc-ip:type { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv4/oc-ip:addresses/oc-ip:address/oc-ip:state/oc-ip:type { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv4/oc-ip:addresses/oc-ip:address/oc-ip:state/oc-ip:origin { deviate not-supported; } @@ -153,6 +186,14 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/oc-ip:config/oc-ip:type { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/oc-ip:state/oc-ip:type { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/oc-ip:state/oc-ip:origin { deviate not-supported; } @@ -173,16 +214,14 @@ module openconfig-interfaces-deviation { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:unnumbered { + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip-ext:autoconf { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:config/oc-ip:enabled { - deviate replace { - default false; - } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:unnumbered { + deviate not-supported; } - + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:config/oc-ip:mtu { deviate not-supported; } @@ -195,6 +234,10 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:config/oc-ip:learn-unsolicited { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:state/oc-ip:mtu { deviate not-supported; } @@ -207,6 +250,10 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:state/oc-ip:learn-unsolicited { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:subinterfaces/oc-intf:subinterface/oc-ip:ipv6/oc-ip:state/oc-ip:counters { deviate not-supported; } @@ -223,6 +270,14 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:config/oc-eth:standalone-link-training { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:config/oc-eth:fec-mode { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:mac-address { deviate not-supported; } @@ -247,20 +302,24 @@ module openconfig-interfaces-deviation { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:config/oc-lag:lag-type { + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:standalone-link-training { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:lag-type { + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:fec-mode { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:lag-speed { + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-1x:dot1x { deviate not-supported; } - deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:member { - deviate not-supported; + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-1x:authenticated-sessions { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-poe:poe { + deviate not-supported; } deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-mac-control-frames { @@ -295,6 +354,54 @@ module openconfig-interfaces-deviation { deviate not-supported; } + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-carrier-errors { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-interrupted-tx { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-late-collision { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-mac-errors-rx { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-single-collision { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-symbol-error { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:in-maxsize-exceeded { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-eth:ethernet/oc-eth:state/oc-eth:counters/oc-eth:out-mac-errors-tx { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:config/oc-lag:lag-type { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:lag-type { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:lag-speed { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-lag:state/oc-lag:member { + deviate not-supported; + } + deviation /oc-intf:interfaces/oc-intf:interface/oc-tun:tunnel { deviate not-supported; } @@ -306,9 +413,17 @@ module openconfig-interfaces-deviation { deviation /oc-intf:interfaces/oc-intf:interface/oc-lag:aggregation/oc-vlan:switched-vlan/oc-vlan:state/oc-vlan:native-vlan { deviate not-supported; } - + deviation /oc-intf:interfaces/oc-intf:interface/oc-vlan:routed-vlan/oc-ip:ipv6/oc-ip:unnumbered { deviate not-supported; } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-if-rates:rates { + deviate not-supported; + } + + deviation /oc-intf:interfaces/oc-intf:interface/oc-intf:penalty-based-aied { + deviate not-supported; + } -} +} \ No newline at end of file diff --git a/models/yang/common/openconfig-if-aggregate.yang b/models/yang/openconfig-if-aggregate.yang similarity index 83% rename from models/yang/common/openconfig-if-aggregate.yang rename to models/yang/openconfig-if-aggregate.yang index a8f18f7f8..078de5129 100644 --- a/models/yang/common/openconfig-if-aggregate.yang +++ b/models/yang/openconfig-if-aggregate.yang @@ -10,8 +10,7 @@ module openconfig-if-aggregate { // import some basic types import openconfig-interfaces { prefix oc-if; } import openconfig-if-ethernet { prefix oc-eth; } - import iana-if-type { prefix ift; } - import openconfig-if-types { prefix oc-ift; } + import iana-if-type { prefix ianaift; } import openconfig-extensions { prefix oc-ext; } // meta @@ -24,7 +23,32 @@ module openconfig-if-aggregate { description "Model for managing aggregated (aka bundle, LAG) interfaces."; - oc-ext:openconfig-version "2.3.2"; + oc-ext:openconfig-version "2.4.5"; + + revision "2025-04-22" { + description + "Updated the definition of lag-speed leaf"; + reference "2.4.5"; + } + + revision "2022-06-28" { + description + "Remove reference to invalid oc-ift type check"; + reference "2.4.4"; + } + + revision "2020-05-01" { + description + "Update when statements to reference config nodes + from config true elements."; + reference "2.4.3"; + } + + revision "2019-04-16" { + description + "Update import prefix for iana-if-type module"; + reference "2.4.2"; + } revision "2018-11-21" { description @@ -129,8 +153,10 @@ module openconfig-if-aggregate { type uint32; units Mbps; description - "Reports effective speed of the aggregate interface, - based on speed of active member interfaces"; + "Reports the effective speed of the aggregate interface, calculated + as the sum of the speeds of member interfaces that are + active (operationally up), forwarding-viable and selected by the + aggregation protocol (e.g., LACP) for active traffic distribution"; } leaf-list member { @@ -201,8 +227,7 @@ module openconfig-if-aggregate { description "Adds LAG configuration to the interface module"; uses aggregation-logical-top { - when "oc-if:state/oc-if:type = 'ift:ieee8023adLag' or " + - "oc-if:state/oc-if:type = 'oc-ift:IF_AGGREGATE'" { + when "oc-if:config/oc-if:type = 'ianaift:ieee8023adLag'" { description "active when the interface is set to type LAG"; } diff --git a/models/yang/openconfig-if-ethernet.yang b/models/yang/openconfig-if-ethernet.yang index 5840cdf13..6c1dae27d 100644 --- a/models/yang/openconfig-if-ethernet.yang +++ b/models/yang/openconfig-if-ethernet.yang @@ -9,7 +9,7 @@ module openconfig-if-ethernet { // import some basic types import openconfig-interfaces { prefix oc-if; } - import iana-if-type { prefix ift; } + import iana-if-type { prefix ianaift; } import openconfig-yang-types { prefix oc-yang; } import openconfig-extensions { prefix oc-ext; } @@ -24,14 +24,90 @@ module openconfig-if-ethernet { "Model for managing Ethernet interfaces -- augments the OpenConfig model for interface configuration and state."; - oc-ext:openconfig-version "2.7.0"; + oc-ext:openconfig-version "2.14.0"; - revision "2024-02-22" { + revision "2024-09-17" { description - "Add 200G, 400G, 600G, and 800G Ethernet Speeds. - Minor formatting fix and fixed a typo in - hardware MAC addr description."; - reference "2.7.0"; + "Refactor config/state nodes to account for physical ethernet vs. + aggregate interface characteristics along with description updates + to indicate applicability."; + reference "2.14.0"; + } + + revision "2023-03-10" { + description + "Allow Ethernet configuration parameters to be + used for aggregate (LAG) interfaces."; + reference "2.13.0"; + } + + revision "2022-04-20" { + description + "Remove unused import"; + reference "2.12.2"; + } + + revision "2021-07-20" { + description + "Fix typo in hardware MAC address description."; + reference "2.12.1"; + } + + revision "2021-07-07" { + description + "Add support for configuring fec-mode per interface."; + reference "2.12.0"; + } + + revision "2021-06-16" { + description + "Remove trailing whitespace."; + reference "2.11.1"; + } + + revision "2021-06-09" { + description + "Add support for standalone link training."; + reference "2.11.0"; + } + + revision "2021-05-17" { + description + "Add ethernet counters: in-carrier-errors, + in-interrupted-tx, in-late-collision, in-mac-errors-rx, + in-single-collision, in-symbol-error and out-mac-errors-tx"; + reference "2.10.0"; + } + + revision "2021-03-30" { + description + "Add counter for drops due to oversized frames."; + reference "2.9.0"; + } + + revision "2020-05-06" { + description + "Minor formatting fix."; + reference "2.8.1"; + } + + revision "2020-05-06" { + description + "Add 200G, 400G, 600G and 800G Ethernet speeds."; + reference "2.8.0"; + } + + revision "2020-05-05" { + description + "Fix when statement checks to use rw paths when + from a rw context."; + reference "2.7.3"; + } + + revision "2019-04-16" { + description + "Update import prefix for iana-if-type module"; + reference "2.7.2"; } revision "2018-11-21" { @@ -98,6 +174,44 @@ module openconfig-if-ethernet { oc-ext:origin "openconfig"; // identity statements + identity INTERFACE_FEC { + description + "Base type to specify FEC modes that can be configured on the interface. + These are FEC modes defined for applying to logical interfaces and their + underlying electrical channels."; + } + + identity FEC_FC { + base INTERFACE_FEC; + description + "Firecode is used for channels with NRZ modulation and speeds less than 100G. + This FEC is designed to comply with the IEEE 802.3, Clause 74."; + } + + identity FEC_RS528 { + base INTERFACE_FEC; + description + "RS528 is used for channels with NRZ modulation. This FEC is designed to + comply with IEEE 802.3, Clause 91."; + } + + identity FEC_RS544 { + base INTERFACE_FEC; + description + "RS544 is used for channels with PAM4 modulation."; + } + + identity FEC_RS544_2X_INTERLEAVE { + base INTERFACE_FEC; + description + "RS544-2x-interleave is used for channels with PAM4 modulation."; + } + + identity FEC_DISABLED { + base INTERFACE_FEC; + description + "FEC is administratively disabled."; + } identity ETHERNET_SPEED { description "base type to specify available Ethernet link @@ -188,7 +302,9 @@ module openconfig-if-ethernet { // grouping statements grouping ethernet-interface-config { - description "Configuration items for Ethernet interfaces"; + description + "Common interface configuration for physical ethernet + logical + aggregate interfaces"; leaf mac-address { type oc-yang:mac-address; @@ -198,6 +314,31 @@ module openconfig-if-ethernet { expected to show the system-assigned MAC address."; } + leaf enable-flow-control { + type boolean; + default false; + description + "Enable or disable flow control for this interface. + Ethernet flow control is a mechanism by which a receiver + may send PAUSE frames to a sender to stop transmission for + a specified time. + + This setting should override auto-negotiated flow control + settings. If left unspecified, and auto-negotiate is TRUE, + flow control mode is negotiated with the peer interface."; + reference + "IEEE 802.3x"; + } + } + + grouping physical-interface-config { + description + "Configuration specific to physical ethernet interfaces. Note + that this grouping is to only apply when the interface `type` is + set to 'ianaift:ethernetCsmacd'. This is not currently restricted + by YANG language statements (must/when) due to uses of this module + within other domains (e.g. wifi)."; + leaf auto-negotiate { type boolean; default true; @@ -210,6 +351,24 @@ module openconfig-if-ethernet { "IEEE 802.3-2012 auto-negotiation transmission parameters"; } + leaf standalone-link-training { + type boolean; + default false; + description + "Link training is automatic tuning of the SerDes transmit and + receive parameters to ensure an optimal connection over copper + links. It is normally run as part of the auto negotiation + sequence as specified in IEEE 802.3 Clause 73. + + Standalone link training is used when full auto negotiation is + not desired on an Ethernet link but link training is needed. + It is configured by setting the standalone-link-training leaf + to TRUE and augo-negotiate leaf to FALSE. + + Note: If auto-negotiate is true, then the value of standalone + link training leaf will be ignored."; + } + leaf duplex-mode { type enumeration { enum FULL { @@ -241,20 +400,13 @@ module openconfig-if-ethernet { by ETHERNET_SPEED identities"; } - leaf enable-flow-control { - type boolean; - default false; + leaf fec-mode { + type identityref { + base INTERFACE_FEC; + } description - "Enable or disable flow control for this interface. - Ethernet flow control is a mechanism by which a receiver - may send PAUSE frames to a sender to stop transmission for - a specified time. - - This setting should override auto-negotiated flow control - settings. If left unspecified, and auto-negotiate is TRUE, - flow control mode is negotiated with the peer interface."; - reference - "IEEE 802.3x"; + "The FEC mode applied to the physical channels associated with + the interface."; } } @@ -349,6 +501,86 @@ module openconfig-if-ethernet { bits within the block"; } + leaf in-carrier-errors { + type oc-yang:counter64; + description + "The number of received errored frames due to a carrier issue. + The value refers to MIB counter for + dot3StatsCarrierSenseErrors + oid=1.3.6.1.2.1.10.7.2.1.11"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-interrupted-tx { + type oc-yang:counter64; + description + "The number of received errored frames due to interrupted + transmission issue. The value refers to MIB counter for + dot3StatsDeferredTransmissions + oid=1.3.6.1.2.1.10.7.2.1.7"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-late-collision { + type oc-yang:counter64; + description + "The number of received errored frames due to late collision + issue. The value refers to MIB counter for + dot3StatsLateCollisions + oid=1.3.6.1.2.1.10.7.2.1.8"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-mac-errors-rx { + type oc-yang:counter64; + description + "The number of received errored frames due to MAC errors + received. The value refers to MIB counter for + dot3StatsInternalMacReceiveErrors + oid=1.3.6.1.2.1.10.7.2.1.16"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-single-collision { + type oc-yang:counter64; + description + "The number of received errored frames due to single collision + issue. The value refers to MIB counter for + dot3StatsSingleCollisionFrames + oid=1.3.6.1.2.1.10.7.2.1.4"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-symbol-error { + type oc-yang:counter64; + description + "The number of received errored frames due to symbol error. + The value refers to MIB counter for + in-symbol-error + oid=1.3.6.1.2.1.10.7.2.1.18"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + + leaf in-maxsize-exceeded { + type oc-yang:counter64; + description + "The total number frames received that are well-formed but + dropped due to exceeding the maximum frame size on the interface + (e.g., MTU or MRU)"; + } + // egress counters leaf out-mac-control-frames { @@ -368,11 +600,28 @@ module openconfig-if-ethernet { description "Number of 802.1q tagged frames sent on the interface"; } + + leaf out-mac-errors-tx { + type oc-yang:counter64; + description + "The number of sent errored frames due to MAC errors + transmitted. The value refers to MIB counter for + dot3StatsInternalMacTransmitErrors + oid=1.3.6.1.2.1.10.7.2.1.10"; + reference + "RFC 1643 Definitions of Managed + Objects for the Ethernet-like Interface Types."; + } + } - grouping ethernet-interface-state { + grouping physical-interface-state { description - "Grouping for defining Ethernet-specific operational state"; + "Grouping for operational state specific to physical ethernet + interfaces. Note that this grouping is to only apply when the + interface `type` is set to 'ianaift:ethernetCsmacd'. This is not + currently restricted by YANG language statements (must/when) due + to uses of this module within other domains (e.g. wifi)."; leaf hw-mac-address { type oc-yang:mac-address; @@ -405,14 +654,17 @@ module openconfig-if-ethernet { completed auto-negotiation with the remote peer, this value shows the interface speed that has been negotiated."; } + } + + grouping ethernet-interface-state { + description + "Common state for physical ethernet and aggregate interfaces"; container counters { description "Ethernet interface counters"; uses ethernet-interface-state-counters; - } - } // data definition statements @@ -429,7 +681,7 @@ module openconfig-if-ethernet { description "Configuration data for ethernet interfaces"; uses ethernet-interface-config; - + uses physical-interface-config; } container state { @@ -438,6 +690,8 @@ module openconfig-if-ethernet { description "State variables for Ethernet interfaces"; uses ethernet-interface-config; + uses physical-interface-config; + uses physical-interface-state; uses ethernet-interface-state; } @@ -452,9 +706,12 @@ module openconfig-if-ethernet { interfaces model"; uses ethernet-top { - when "oc-if:state/oc-if:type = 'ift:ethernetCsmacd'" { - description "Additional interface configuration parameters when - the interface type is Ethernet"; + when "oc-if:config/oc-if:type = 'ianaift:ethernetCsmacd' or " + + "oc-if:config/oc-if:type = 'ianaift:ieee8023adLag'" { + description + "Additional interface configuration parameters when + the interface type is Ethernet, or the interface is an aggregate + interface."; } } } diff --git a/models/yang/openconfig-if-ip.yang b/models/yang/openconfig-if-ip.yang index c65269181..fd216f60f 100644 --- a/models/yang/openconfig-if-ip.yang +++ b/models/yang/openconfig-if-ip.yang @@ -44,7 +44,50 @@ module openconfig-if-ip { Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info)."; - oc-ext:openconfig-version "3.0.0"; + oc-ext:openconfig-version "3.6.0"; + + revision "2024-05-28" { + description + "Add gratuitous-arp-accepted for IPv4 and unsolicited-na-accepted for IPv6."; + reference "3.6.0"; + } + + revision "2024-03-13" { + description + "Update in-pkts and out-pkts descriptions."; + reference "3.5.1"; + } + + revision "2023-08-14" { + description + "Add multicast counters for IPv4, IPv6."; + reference "3.5.0"; + } + +revision "2023-06-30" { + description + "Deprecate IPv6 router advertisment config suppress leaf and add config + mode leaf."; + reference "3.4.0"; + } + + revision "2023-04-12" { + description + "Add ipv4 address type configuration."; + reference "3.3.0"; + } + + revision "2023-02-06" { + description + "Add IPv6 link-local configuration."; + reference "3.2.0"; + } + + revision "2022-11-09" { + description + "Add additional IPv6 router-advertisement features."; + reference "3.1.0"; + } revision "2019-01-08" { description @@ -161,6 +204,24 @@ module openconfig-if-ip { "The origin of a neighbor entry."; } + typedef ipv4-address-type { + type enumeration { + enum PRIMARY { + description + "The primary address on the interface. There can only be one primary + address associated on an interface."; + } + enum SECONDARY { + description + "Secondary address on an interface. There can be multiple secondary + addresses associated on an interface."; + } + } + + description + "The type of an IPv4 address."; + } + // grouping statements grouping ip-common-global-config { @@ -192,7 +253,8 @@ module openconfig-if-ip { type oc-yang:counter64; description "The total number of IP packets received for the specified - address family, including those received in error"; + address family, including all IP unicast, multicast, + broadcast and error packets."; reference "RFC 4293 - Management Information Base for the Internet Protocol (IP)"; @@ -209,6 +271,31 @@ module openconfig-if-ip { Internet Protocol (IP)"; } + leaf in-multicast-pkts { + type oc-yang:counter64; + description + "The number of IP packets received for the specified + address family that are multicast packets. + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 4293: Management Information Base for the Internet + Protocol (IP) - ipSystemStatsHCInMcastPkts"; + } + + leaf in-multicast-octets { + type oc-yang:counter64; + description + "The total number of octets received in input IP + multicast packets for the specified address + family, including those received in error."; + reference + "RFC 4293: Management Information Base for the Internet + Protocol (IP) - ipSystemStatsHCInMcastOctets"; + } + leaf in-error-pkts { // TODO: this counter combines several error conditions -- // could consider breaking them out to separate leaf nodes @@ -267,7 +354,8 @@ module openconfig-if-ip { specified address family that the device supplied to the lower layers for transmission. This includes packets generated locally and those forwarded by the - device."; + device as well as unicast, multicast and broadcast + packets."; reference "RFC 4293 - Management Information Base for the Internet Protocol (IP)"; @@ -286,6 +374,32 @@ module openconfig-if-ip { Internet Protocol (IP)"; } + leaf out-multicast-pkts { + type oc-yang:counter64; + description + "The total number of IP multicast packets transmitted. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP) + - ipSystemStatsHCOutMcastPkts"; + } + + leaf out-multicast-octets { + type oc-yang:counter64; + description + "The total number of IP multicast octets transmitted. This + includes packets generated locally and those forwarded by + the device."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + leaf out-error-pkts { // TODO: this counter combines several error conditions -- // could consider breaking them out to separate leaf nodes @@ -372,13 +486,21 @@ module openconfig-if-ip { "RFC 791: Internet Protocol"; } + leaf gratuitous-arp-accepted { + type boolean; + description + "When set to true, gratuitous ARPs will be accepted and + the ARP table will be updated."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + } + uses ip-common-global-config; } grouping ipv4-address-config { - description "Per IPv4 adresss configuration data for the interface."; @@ -396,6 +518,16 @@ module openconfig-if-ip { description "The length of the subnet prefix."; } + + leaf type { + type ipv4-address-type; + default PRIMARY; + description + "Specifies the explicit type of the IPv4 address being assigned + to the interface. By default, addresses are assumed to be a primary address. + Where secondary addresses is to be configured, this leaf should be set + to SECONDARY."; + } } grouping ipv4-neighbor-config { @@ -488,6 +620,30 @@ module openconfig-if-ip { "RFC 4862: IPv6 Stateless Address Autoconfiguration"; } + leaf learn-unsolicited { + type enumeration { + enum NONE { + value 0; + } + enum GLOBAL { + value 1; + } + enum LINK_LOCAL { + value 2; + } + enum BOTH { + value 3; + } + } + default "NONE"; + description + "Sets if neighbors should be learned from unsolicited neighbor + advertisements for global or link local addresses or both."; + reference + "RFC 9131: Routers Creating Cache Entries upon + Receiving Unsolicited Neighbor Advertisements"; + } + uses ip-common-global-config; } @@ -508,6 +664,17 @@ module openconfig-if-ip { description "The length of the subnet prefix."; } + + leaf type { + type oc-inet:ipv6-address-type; + default GLOBAL_UNICAST; + description + "Specifies the explicit type of the IPv6 address being assigned + to the interface. By default, addresses are assumed to be + global unicast. Where a link-local address is to be explicitly + configured, this leaf should be set to LINK_LOCAL."; + } + } grouping ipv6-address-state { @@ -883,6 +1050,15 @@ module openconfig-if-ip { description "Configuration parameters for IPv6 router advertisements."; + leaf enable { + type boolean; + default true; + description + "If set to false, all IPv6 router advertisement functions are + disabled. The local system will not transmit router advertisement + messages and will not respond to router solicitation messages."; + } + leaf interval { type uint32; units seconds; @@ -901,12 +1077,112 @@ module openconfig-if-ip { } leaf suppress { + status deprecated; type boolean; default false; description "When set to true, router advertisement neighbor discovery messages are not transmitted on this interface."; } + + leaf mode { + type enumeration { + enum ALL { + description + "The system will transmit unsolicited router advertisement + messages and respond to router solicitation requests."; + } + enum DISABLE_UNSOLICITED_RA { + description + "Unsolicted router advertisement messages are not transmitted on + this interface. Responses to router solicitation messages will + be transmitted."; + } + } + default "ALL"; + description + "Mode controls which set of behaviors the local system should perform + to support IPv6 router advertisements."; + reference "RFC4861: Neighbor Discovery for IP version 6 (IPv6)"; + } + + leaf managed { + type boolean; + default false; + description + "When set to true, the managed address configuration (M) flag is set in + the advertised router advertisement. The M flag indicates that there are + addresses available via DHCPv6."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.2"; + } + + leaf other-config { + type boolean; + default false; + description + "When set to true, the other configuration (O) flag is set in the + advertised router advertisement. The O flag indicates that there is + other configuration available via DHCPv6 (e.g., DNS servers)."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.2"; + } + } + + grouping ipv6-ra-prefix-config { + description + "Configuration parameters for an individual prefix within an IPv6 + router advertisement."; + + leaf prefix { + type oc-inet:ipv6-prefix; + description + "IPv6 prefix to be advertised within the router advertisement + message."; + } + + leaf valid-lifetime { + type uint32; + units seconds; + description + "The length of time that the prefix is valid relative to the time + the packet was sent."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.6.2"; + } + + leaf preferred-lifetime { + type uint32; + units seconds; + description + "The length of time that the address within the prefix remains + in the preferred state, i.e., unrestricted use is allowed by + upper-layer protocols. See RFC4862 for a complete definition + of preferred behaviours."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.6.2"; + } + + leaf disable-advertisement { + type boolean; + description + "When set to true, the prefix is not advertised within + router advertisement messages that are sent as a result of + router soliciation messages."; + } + + leaf disable-autoconfiguration { + type boolean; + description + "When set to true, the prefix is marked as not to be used for stateless + address configuration. This is achieved by setting the autonomous address + configuration bit for the prefix."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.6.1"; + } + + leaf enable-onlink { + type boolean; + description + "When set to true, the prefix is marked as being on link by setting the + L-bit for the prefix within a router advertisement."; + reference "RFC4861: Neighbor Discovery for IPv6, section 4.6.1"; + } } grouping ipv4-proxy-arp-config { @@ -1142,6 +1418,52 @@ module openconfig-if-ip { advertisements for IPv6."; uses ipv6-ra-config; } + + container prefixes { + description + "Container for a list of prefixes that are included in the + router advertisement message."; + + list prefix { + key "prefix"; + + description + "List of prefixes that are to be included in the IPv6 + router-advertisement messages for the interface. The list + is keyed by the IPv6 prefix in CIDR representation. + + Prefixes that are listed are those that are to be + advertised in router advertisement messages. Where there + are IPv6 global addresses configured on an interface and + the prefix is not listed in the prefix list, it MUST NOT + be advertised in the router advertisement message."; + + leaf prefix { + type leafref { + path "../config/prefix"; + } + description + "Reference to the IPv6 prefix key for the prefix list."; + } + + container config { + description + "Configuration parameters corresponding to an IPv6 prefix + within the router advertisement."; + + uses ipv6-ra-prefix-config; + } + + container state { + config false; + description + "Operational state parameters corresponding to an IPv6 prefix + within the router advertisement."; + + uses ipv6-ra-prefix-config; + } + } + } } container neighbors { diff --git a/models/yang/openconfig-interfaces.yang b/models/yang/openconfig-interfaces.yang index f3e0feeac..3f739a632 100644 --- a/models/yang/openconfig-interfaces.yang +++ b/models/yang/openconfig-interfaces.yang @@ -12,6 +12,7 @@ module openconfig-interfaces { import openconfig-yang-types { prefix oc-yang; } import openconfig-types { prefix oc-types; } import openconfig-extensions { prefix oc-ext; } + import openconfig-transport-types { prefix oc-opt-types; } // meta organization "OpenConfig working group"; @@ -50,7 +51,83 @@ module openconfig-interfaces { Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info)."; - oc-ext:openconfig-version "2.4.1"; + oc-ext:openconfig-version "3.8.0"; + + revision "2024-12-05" { + description + "Add interface-transitions and link-transitions counters"; + reference + "3.8.0"; + } + + revision "2024-12-05" { + description + "Description typo for unnumbered/interface-ref/config/subinterface leaf"; + reference + "3.7.2"; + } + + revision "2024-04-04" { + description + "Use single quotes in descriptions."; + reference + "3.7.1"; + } + + revision "2023-11-06" { + description + "Clarify description for admin-status TESTING."; + reference + "3.7.0"; + } + + revision "2023-08-29" { + description + "Add augment for penalty-based additive-increase, exponential-decrease link damping algorithm."; + reference + "3.6.0"; + } + + revision "2023-07-14" { + description + "Move counters which apply to both interfaces and subinterfaces to + a common grouping. Deprecate physical counters from subinterface"; + reference "3.5.0"; + } + + revision "2023-02-06" { + description + "Add further specification to interface-ref type to + clarify that the interface and subinterface leaves + are how an interface is referenced, regardless of + context."; + reference "3.0.2"; + } + + revision "2022-10-25" { + description + "change loopback-mode to align with available modes"; + reference "3.0.1"; + } + + revision "2021-04-06" { + description + "Add leaves for management and cpu interfaces"; + reference "2.5.0"; + } + + revision "2019-11-19" { + description + "Update description of interface name."; + reference "2.4.3"; + } + + revision "2019-07-10" { + description + "Remove redundant nanosecond units statements to reflect + universal definition of timeticks64 type."; + reference "2.4.2"; + } revision "2018-11-21" { description @@ -167,7 +244,7 @@ module openconfig-interfaces { "Reference to a subinterface -- this requires the base interface to be specified using the interface leaf in this container. If only a reference to a base interface - is requuired, this leaf should not be set."; + is required, this leaf should not be set."; } } @@ -192,7 +269,19 @@ module openconfig-interfaces { container interface-ref { description - "Reference to an interface or subinterface"; + "Reference to an interface or subinterface. The interface + that is being referenced is uniquely referenced based on + the specified interface and subinterface leaves. In contexts + where a Layer 3 interface is to be referenced, both the + interface and subinterface leaves must be populated, as + Layer 3 configuration within the OpenConfig models is + associated with a subinterface. In the case where a + Layer 2 interface is to be referenced, only the + interface is specified. + + The interface/subinterface leaf tuple must be used as + the means by which the interface is specified, regardless + of any other context information (e.g., key in a list)."; container config { description @@ -371,12 +460,11 @@ module openconfig-interfaces { } leaf loopback-mode { - type boolean; - default false; + type oc-opt-types:loopback-mode-type; description - "When set to true, the interface is logically looped back, - such that packets that are forwarded via the interface - are received on the same interface."; + "Sets the loopback type on the interface. Setting the + mode to something besides NONE activates the loopback in + the specified mode."; } uses interface-common-config; @@ -444,6 +532,75 @@ module openconfig-interfaces { } } + grouping interface-link-damping-config { + description + "Configuration data for interface link damping settings."; + + leaf max-suppress-time { + type uint32; + units milliseconds; + default 0; + description + "Maximum time an interface can remain damped since the last link down event no matter how unstable it has been prior to this period of stability. In a damped state, the interface's state change will not be advertised."; + } + + leaf decay-half-life { + type uint32; + units milliseconds; + default 0; + description + "The amount of time after which an interface's penalty is decreased by half. Decay-half-time should not be more than max-suppress-time."; + } + + leaf suppress-threshold { + type uint32; + default 0; + description + "The accumulated penalty that triggers the damping of an interface. A value of 0 indicates config is disabled."; + } + + leaf reuse-threshold { + type uint32; + default 0; + description + "When the accumulated penalty decreases to this reuse threshold, the interface is not damped anymore. Interface state changes are advertised to applications. A value of 0 indicates config is disabled."; + } + + leaf flap-penalty { + type uint32; + default 0; + description + "A penalty that each down event costs. A value of 0 indicates the config is disabled."; + } + } + grouping interface-link-damping-state { + description + "Operational state data for interface link damping settings."; + } + grouping link-damping-top { + description + "Top level grouping for link damping parameters."; + + container penalty-based-aied { + description + "Top level container to suppress UP->DOWN link events using a penalty based additive-increase, exponential-decrease algorithm."; + + container config { + description + "Configuration data for link damping settings."; + uses interface-link-damping-config; + } + + container state { + config false; + description + "Operational state data for link damping settings."; + uses interface-link-damping-config; + uses interface-link-damping-state; + } + } + } + grouping interface-common-state { description "Operational state data (in addition to intended configuration) @@ -472,11 +629,16 @@ module openconfig-interfaces { "Not ready to pass packets and not in some test mode."; } enum TESTING { - //TODO: This is generally not supported as a configured - //admin state, though it's in the standard interfaces MIB. - //Consider removing it. description - "In some test mode."; + "The interface should be treated as if in admin-down state for + control plane protocols. In addition, while in TESTING state the + device should remove the interface from aggregate interfaces. + An interface transition to the TESTING state based on a qualification + workflow, or internal device triggered action - such as the gNOI Link + Qualification service"; + reference + "gNOI Link Qualification Service + https://github.com/openconfig/gnoi/blob/main/packet_link_qualification/index.md"; } } //TODO:consider converting to an identity to have the @@ -508,7 +670,7 @@ module openconfig-interfaces { enum TESTING { value 3; description - "In some test mode. No operational packets can + "In test mode. No operational packets can be passed."; } enum UNKNOWN { @@ -547,7 +709,6 @@ module openconfig-interfaces { leaf last-change { type oc-types:timeticks64; - units nanoseconds; description "This timestamp indicates the absolute time of the last state change of the interface (e.g., up-to-down transition). @@ -568,301 +729,430 @@ module openconfig-interfaces { channel on the system."; oc-ext:telemetry-on-change; } - } + leaf management { + type boolean; + description + "When set to true, the interface is a dedicated + management interface that is not connected to dataplane + interfaces. It may be used to connect the system to an + out-of-band management network, for example."; + oc-ext:telemetry-on-change; + } - grouping interface-counters-state { + leaf cpu { + type boolean; + description + "When set to true, the interface is for traffic + that is handled by the system CPU, sometimes also called the + control plane interface. On systems that represent the CPU + interface as an Ethernet interface, for example, this leaf + should be used to distinguish the CPU interface from dataplane + interfaces."; + oc-ext:telemetry-on-change; + } + } + + grouping interface-common-counters-state { description - "Operational state representing interface counters - and statistics."; + "Operational state representing interface counters and statistics + applicable to (physical) interfaces and (logical) subinterfaces."; - //TODO: we may need to break this list of counters into those - //that would appear for physical vs. subinterface or logical - //interfaces. For now, just replicating the full stats - //grouping to both interface and subinterface. + leaf in-octets { + type oc-yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. - oc-ext:operational; + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - container counters { + leaf in-pkts { + type oc-yang:counter64; description - "A collection of interface-related statistics objects."; + "The total number of packets received on the interface, + including all unicast, multicast, broadcast and bad packets + etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information Base. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-octets { - type oc-yang:counter64; - description - "The total number of octets received on the interface, - including framing characters. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; - } + leaf in-unicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. - leaf in-pkts { - type oc-yang:counter64; - description - "The total number of packets received on the interface, - including all unicast, multicast, broadcast and bad packets - etc."; - reference - "RFC 2819: Remote Network Monitoring Management Information - Base"; - } + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-unicast-pkts { - type oc-yang:counter64; - description - "The number of packets, delivered by this sub-layer to a - higher (sub-)layer, that were not addressed to a - multicast or broadcast address at this sub-layer. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; - } + leaf in-broadcast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. - leaf in-broadcast-pkts { - type oc-yang:counter64; - description - "The number of packets, delivered by this sub-layer to a - higher (sub-)layer, that were addressed to a broadcast - address at this sub-layer. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - - ifHCInBroadcastPkts"; - } + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInBroadcastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-multicast-pkts { - type oc-yang:counter64; - description - "The number of packets, delivered by this sub-layer to a - higher (sub-)layer, that were addressed to a multicast - address at this sub-layer. For a MAC-layer protocol, - this includes both Group and Functional addresses. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - - ifHCInMulticastPkts"; - } + leaf in-multicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInMulticastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-discards { - type oc-yang:counter64; - description - "The number of inbound packets that were chosen to be - discarded even though no errors had been detected to - prevent their being deliverable to a higher-layer - protocol. One possible reason for discarding such a - packet could be to free up buffer space. + leaf in-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; + leaf in-discards { + type oc-yang:counter64; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifInDiscards"; - } + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-errors { - type oc-yang:counter64; - description - "For packet-oriented interfaces, the number of inbound - packets that contained errors preventing them from being - deliverable to a higher-layer protocol. For character- - oriented or fixed-length interfaces, the number of - inbound transmission units that contained errors - preventing them from being deliverable to a higher-layer - protocol. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifInErrors"; - } + leaf out-octets { + type oc-yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. - leaf in-unknown-protos { - type oc-yang:counter64; - description - "For packet-oriented interfaces, the number of packets - received via the interface that were discarded because - of an unknown or unsupported protocol. For - character-oriented or fixed-length interfaces that - support protocol multiplexing, the number of - transmission units received via the interface that were - discarded because of an unknown or unsupported protocol. - For any interface that does not support protocol - multiplexing, this counter is not present. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; - } + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf in-fcs-errors { - type oc-yang:counter64; - description - "Number of received packets which had errors in the - frame check sequence (FCS), i.e., framing errors. + leaf out-pkts { + type oc-yang:counter64; + description + "The total number of packets transmitted out of the + interface, including all unicast, multicast, broadcast, + and bad packets etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information Base. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - Discontinuities in the value of this counter can occur - when the device is re-initialization as indicated by the - value of 'last-clear'."; - } + leaf out-unicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf out-octets { - type oc-yang:counter64; - description - "The total number of octets transmitted out of the - interface, including framing characters. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; - } + leaf out-broadcast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutBroadcastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf out-pkts { - type oc-yang:counter64; - description - "The total number of packets transmitted out of the - interface, including all unicast, multicast, broadcast, - and bad packets etc."; - reference - "RFC 2819: Remote Network Monitoring Management Information - Base"; - } + leaf out-multicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutMulticastPkts. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf out-unicast-pkts { - type oc-yang:counter64; - description - "The total number of packets that higher-level protocols - requested be transmitted, and that were not addressed - to a multicast or broadcast address at this sub-layer, - including those that were discarded or not sent. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; - } + leaf out-discards { + type oc-yang:counter64; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } - leaf out-broadcast-pkts { - type oc-yang:counter64; - description - "The total number of packets that higher-level protocols - requested be transmitted, and that were addressed to a - broadcast address at this sub-layer, including those - that were discarded or not sent. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - - ifHCOutBroadcastPkts"; - } + leaf out-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors. + RFC 4293: Management Information Base for the + Internet Protocol (IP)."; + } + leaf last-clear { + type oc-types:timeticks64; + description + "Timestamp of the last time the interface counters were + cleared. - leaf out-multicast-pkts { - type oc-yang:counter64; - description - "The total number of packets that higher-level protocols - requested be transmitted, and that were addressed to a - multicast address at this sub-layer, including those - that were discarded or not sent. For a MAC-layer - protocol, this includes both Group and Functional - addresses. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - - ifHCOutMulticastPkts"; - } + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + oc-ext:telemetry-on-change; + } + } - leaf out-discards { - type oc-yang:counter64; - description - "The number of outbound packets that were chosen to be - discarded even though no errors had been detected to - prevent their being transmitted. One possible reason - for discarding such a packet could be to free up buffer - space. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; - } + grouping interface-counters-state { + description + "Operational state representing interface counters + and statistics."; - leaf out-errors { - type oc-yang:counter64; - description - "For packet-oriented interfaces, the number of outbound - packets that could not be transmitted because of errors. - For character-oriented or fixed-length interfaces, the - number of outbound transmission units that could not be - transmitted because of errors. - - Discontinuities in the value of this counter can occur - at re-initialization of the management system, and at - other times as indicated by the value of - 'last-clear'."; - reference - "RFC 2863: The Interfaces Group MIB - ifOutErrors"; - } + oc-ext:operational; - leaf carrier-transitions { - type oc-yang:counter64; - description - "Number of times the interface state has transitioned - between up and down since the time the device restarted - or the last-clear time, whichever is most recent."; - oc-ext:telemetry-on-change; - } + leaf in-unknown-protos { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } - leaf last-clear { - type oc-types:timeticks64; - units nanoseconds; - description - "Timestamp of the last time the interface counters were - cleared. + leaf in-fcs-errors { + type oc-yang:counter64; + description + "Number of received packets which had errors in the + frame check sequence (FCS), i.e., framing errors. - The value is the timestamp in nanoseconds relative to - the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; - oc-ext:telemetry-on-change; - } + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + } + + leaf carrier-transitions { + type oc-yang:counter64; + status deprecated; + description + "Number of times the interface state has transitioned + between up and down since the time the device restarted + or the last-clear time, whichever is most recent. + + Please use interface-transitions instead, which has + similar, but more precisely specified, semantics and a + clearer name."; + oc-ext:telemetry-on-change; + } + + leaf interface-transitions { + type oc-yang:counter64; + description + "The total number of times the interface state (oper-status) + has either transitioned to 'UP' state from any other state, or + from state 'UP' to any other state. I.e., an interface flap + from UP to DOWN back to UP increments the counter by 2. + Transitions between any other interface states other than to + or from 'UP' state are not included in the counter. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + oc-ext:telemetry-on-change; + } + + leaf link-transitions { + type oc-yang:counter64; + description + "This is the number of times that the underlying link state + (e.g., at the optical receiver) has transitioned to or from + 'UP' state before any holdtime, dampening, or other processing + has been applied that could suppress an update to the interface + 'oper-status' and corresponding interface-transitions counter. + + The counter is incremented both when the link transitions + to 'UP' state from any other link state and also when the link + transitions from 'UP' state to any other link state, i.e., an + interface flap from UP to DOWN back to UP increments the + counter by 2. + + Implementations are not required to count all transitions, + e.g., if they are below the level of granularity monitored by + the system, and hence may not tally with the equivalent counter + on the remote end of the link. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + oc-ext:telemetry-on-change; + } + + leaf resets { + type oc-yang:counter64; + description + "Number of times the interface hardware has been reset. The + triggers and effects of this event are hardware-specifc."; + oc-ext:telemetry-on-change; + + } + } + + grouping subinterfaces-counters-state { + description + "Operational state representing counters unique to subinterfaces"; + + oc-ext:operational; + leaf in-unknown-protos { + type oc-yang:counter64; + status deprecated; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf in-fcs-errors { + type oc-yang:counter64; + status deprecated; + description + "Number of received packets which had errors in the + frame check sequence (FCS), i.e., framing errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of 'last-clear'."; } + + leaf carrier-transitions { + type oc-yang:counter64; + status deprecated; + description + "Number of times the interface state has transitioned + between up and down since the time the device restarted + or the last-clear time, whichever is most recent."; + oc-ext:telemetry-on-change; + } + } // data definition statements @@ -954,7 +1244,15 @@ module openconfig-interfaces { } uses interface-common-state; - uses interface-counters-state; + + container counters { + description + "A collection of interface specific statistics entitites which are + not common to subinterfaces."; + + uses interface-common-counters-state; + uses subinterfaces-counters-state; + } } grouping subinterfaces-top { @@ -1026,7 +1324,7 @@ module openconfig-interfaces { path "../config/name"; } description - "References the configured name of the interface"; + "References the name of the interface"; //TODO: need to consider whether this should actually //reference the name in the state subtree, which //presumably would be the system-assigned name, or the @@ -1053,10 +1351,27 @@ module openconfig-interfaces { uses interface-phys-config; uses interface-common-state; - uses interface-counters-state; + + container counters { + description + "A collection of interface specific statistics entitites which are + not common to subinterfaces."; + + uses interface-common-counters-state; + uses interface-counters-state; + } } - uses interface-phys-holdtime-top; + uses interface-phys-holdtime-top { + when "./penalty-based-aied/config/suppress-threshold = 0 + or ./penalty-based-aied/config/reuse-threshold = 0 + or ./penalty-based-aied/config/flap-penalty = 0" { + description + "Hold time and penalty-based-aied are two algorithms to suppress + link transitions and must be mutually exclusive."; + } + } + uses link-damping-top; uses subinterfaces-top; } } diff --git a/models/yang/sonic/import.mk b/models/yang/sonic/import.mk index e28360c30..4ae0ecf2a 100644 --- a/models/yang/sonic/import.mk +++ b/models/yang/sonic/import.mk @@ -12,4 +12,5 @@ SONICYANG_IMPORTS += sonic-portchannel.yang SONICYANG_IMPORTS += sonic-vlan.yang SONICYANG_IMPORTS += sonic-mclag.yang SONICYANG_IMPORTS += sonic-types.yang -SONICYANG_IMPORTS += sonic-vrf.yang \ No newline at end of file +SONICYANG_IMPORTS += sonic-vrf.yang +SONICYANG_IMPORTS += sonic-loopback-interface.yang diff --git a/translib/transformer/interfaces_openconfig_test.go b/translib/transformer/interfaces_openconfig_test.go index ee2a92a4e..066c2a3de 100644 --- a/translib/transformer/interfaces_openconfig_test.go +++ b/translib/transformer/interfaces_openconfig_test.go @@ -22,6 +22,7 @@ package transformer_test import ( + "errors" "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/tlerr" "testing" @@ -29,7 +30,11 @@ import ( ) func Test_openconfig_interfaces(t *testing.T) { - var url, url_input_body_json string + var url, url_input_body_json, expected_get_json string + var pre_req_map, cleanuptbl map[string]interface{} + + invalid_uri_err_msg := "Invalid URI" + invalid_uri_err := tlerr.TranslibSyntaxValidationError{ErrorStr: errors.New(invalid_uri_err_msg)} t.Log("\n\n+++++++++++++ CONFIGURING INTERFACES ATTRIBUTES ++++++++++++") t.Log("\n\n--- PATCH interfaces config---") @@ -40,10 +45,43 @@ func Test_openconfig_interfaces(t *testing.T) { t.Log("\n\n--- Verify PATCH interfaces config ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config" - expected_get_json := "{\"openconfig-interfaces:config\": {\"description\": \"UT_Interface\", \"enabled\": false, \"mtu\": 8900, \"name\": \"Ethernet0\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"UT_Interface\", \"enabled\": false, \"mtu\": 8900, \"name\": \"Ethernet0\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" t.Run("Test GET on interface config", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH interfaces config without key ---") + url = "/openconfig-interfaces:interfaces/interface/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"mtu\": 8900, \"description\": \"UT_Interface\", \"enabled\": false}}" + t.Run("Test PATCH on interface config", processSetRequest(url, url_input_body_json, "PATCH", true, invalid_uri_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interfaces config without key ---") + url = "/openconfig-interfaces:interfaces/interface/config/name" + expected_get_json = "{}" + binding_failed_err_msg := "parent container device (type *ocbinds.Device): JSON contains unexpected field name" + binding_failed_err := errors.New(binding_failed_err_msg) + t.Run("Test GET on interface config", processGetRequest(url, nil, expected_get_json, true, binding_failed_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interfaces name without key ---") + url = "/openconfig-interfaces:interfaces/interface/name" + expected_get_json = "{}" + t.Run("Test GET on interface config", processGetRequest(url, nil, expected_get_json, true, binding_failed_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interface config/name node invalid name ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/name" + url_input_body_json = "{\"openconfig-interfaces:name\": \"invalid-name\"}" + name_err_str := "Invalid interface config/name received" + name_err := errors.New(name_err_str) + t.Run("Test PATCH on interface config/name negative case", processSetRequest(url, url_input_body_json, "PATCH", true, name_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete interface config/name node negative case ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/name" + t.Run("Test DELETE on interface config/name negative case", processDeleteRequest(url, true, name_err)) + time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH interface leaf nodes---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/enabled" url_input_body_json = "{\"openconfig-interfaces:enabled\": true}" @@ -56,24 +94,36 @@ func Test_openconfig_interfaces(t *testing.T) { time.Sleep(1 * time.Second) url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/description" - url_input_body_json = "{\"openconfig-interfaces:description\": \"\"}" + url_input_body_json = "{\"openconfig-interfaces:description\": \"test desc\"}" t.Run("Test PATCH on interface description", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) time.Sleep(1 * time.Second) - cleanuptbl := map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet0": ""}} + cleanuptbl = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet0": ""}} unloadDB(db.ApplDB, cleanuptbl) - pre_req_map := map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet0": map[string]interface{}{"admin_status": "up", "mtu": "9000"}}} + pre_req_map = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet0": map[string]interface{}{"admin_status": "up", "mtu": "9000"}}} loadDB(db.ApplDB, pre_req_map) t.Log("\n\n--- Verify PATCH interface leaf nodes ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/state" - expected_get_json = "{\"openconfig-interfaces:state\": { \"admin-status\": \"UP\", \"counters\": {\"in-broadcast-pkts\": \"0\", \"in-discards\": \"0\", \"in-errors\": \"0\", \"in-multicast-pkts\": \"0\", \"in-octets\": \"0\", \"in-pkts\": \"0\", \"in-unicast-pkts\": \"0\", \"out-broadcast-pkts\": \"0\", \"out-discards\": \"0\", \"out-errors\": \"0\", \"out-multicast-pkts\": \"0\", \"out-octets\": \"0\", \"out-pkts\": \"0\", \"out-unicast-pkts\": \"0\"}, \"enabled\": true, \"mtu\": 9000, \"name\": \"Ethernet0\"}}" + expected_get_json = "{\"openconfig-interfaces:state\": { \"admin-status\": \"UP\", \"counters\": {\"in-broadcast-pkts\": \"0\", \"in-discards\": \"0\", \"in-errors\": \"0\", \"in-multicast-pkts\": \"0\", \"in-octets\": \"0\", \"in-pkts\": \"0\", \"in-unicast-pkts\": \"0\", \"out-broadcast-pkts\": \"0\", \"out-discards\": \"0\", \"out-errors\": \"0\", \"out-multicast-pkts\": \"0\", \"out-octets\": \"0\", \"out-pkts\": \"0\", \"out-unicast-pkts\": \"0\"}, \"enabled\": true, \"mtu\": 9000, \"name\": \"Ethernet0\", \"type\": \"iana-if-type:ethernetCsmacd\", \"logical\": false, \"management\": false, \"cpu\": false}}" t.Run("Test GET on interface state", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--verify PATCH interface state - leaf node mtu --") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/state/mtu" + expected_get_json = "{\"openconfig-interfaces:mtu\": 9000}" + t.Run("Test GET on interface state mtu", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--verify PATCH interface state - leaf node mtu --") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/state/mtu" + expected_get_json = "{\"openconfig-interfaces:mtu\": 9000}" + t.Run("Test GET on interface state mtu", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + t.Log("\n\n--- DELETE at interface enabled ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/enabled" - t.Run("Test DELETE on interface enabled", processDeleteRequest(url, true)) + t.Run("Test DELETE on interface enabled", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at interface enabled ---") @@ -84,7 +134,7 @@ func Test_openconfig_interfaces(t *testing.T) { t.Log("\n\n--- DELETE at interface mtu ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/mtu" - t.Run("Test DELETE on interface mtu", processDeleteRequest(url, true)) + t.Run("Test DELETE on interface mtu", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at interface mtu ---") @@ -102,7 +152,7 @@ func Test_openconfig_interfaces(t *testing.T) { t.Log("\n\n--- DELETE at interface description ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/description" - t.Run("Test DELETE on interface description", processDeleteRequest(url, true)) + t.Run("Test DELETE on interface description", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at interface description ---") @@ -125,11 +175,100 @@ func Test_openconfig_interfaces(t *testing.T) { t.Log("\n\n--- Verify PATCH interface ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/state" - expected_get_json = "{\"openconfig-interfaces:state\": { \"admin-status\": \"UP\", \"counters\": {\"in-broadcast-pkts\": \"0\", \"in-discards\": \"0\", \"in-errors\": \"0\", \"in-multicast-pkts\": \"0\", \"in-octets\": \"0\", \"in-pkts\": \"0\", \"in-unicast-pkts\": \"0\", \"out-broadcast-pkts\": \"0\", \"out-discards\": \"0\", \"out-errors\": \"0\", \"out-multicast-pkts\": \"0\", \"out-octets\": \"0\", \"out-pkts\": \"0\", \"out-unicast-pkts\": \"0\"}, \"enabled\": true, \"mtu\": 9100, \"name\": \"Ethernet0\"}}" + expected_get_json = "{\"openconfig-interfaces:state\": { \"admin-status\": \"UP\", \"counters\": {\"in-broadcast-pkts\": \"0\", \"in-discards\": \"0\", \"in-errors\": \"0\", \"in-multicast-pkts\": \"0\", \"in-octets\": \"0\", \"in-pkts\": \"0\", \"in-unicast-pkts\": \"0\", \"out-broadcast-pkts\": \"0\", \"out-discards\": \"0\", \"out-errors\": \"0\", \"out-multicast-pkts\": \"0\", \"out-octets\": \"0\", \"out-pkts\": \"0\", \"out-unicast-pkts\": \"0\"}, \"enabled\": true, \"mtu\": 9100, \"name\": \"Ethernet0\", \"type\": \"iana-if-type:ethernetCsmacd\", \"logical\": false, \"management\": false, \"cpu\": false}}" + t.Run("Test GET on interface state", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + unloadDB(db.ApplDB, cleanuptbl) + + t.Log("\n\n+++++++++++++ Performing Delete on interfaces/interface[name=Ethernet88]/config node ++++++++++++") + pre_req_map = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet88": map[string]interface{}{"mtu": "9100"}}} + loadDB(db.ConfigDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet88]/config" + del_not_supported_msg := "Delete operation not supported for this path - /openconfig-interfaces:interfaces/interface/config" + del_not_supported := tlerr.InvalidArgsError{Format: del_not_supported_msg} + t.Run("Test delete on interfaces/interface[name=Ethernet88]/config node", processDeleteRequest(url, true, del_not_supported)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet88": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + + t.Log("\n\n--- Input range validation for mtu ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/mtu" + url_input_body_json = "{\"openconfig-interfaces:mtu\": 99999}" + mtu_err := tlerr.TranslibSyntaxValidationError{ErrorStr: errors.New("error parsing 99999 for schema mtu: value 99999 falls outside the int range [0, 65535]")} + t.Run("Test PATCH on interface mtu out-of-range", processSetRequest(url, url_input_body_json, "PATCH", true, mtu_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interfaces type ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"mtu\": 8900, \"description\": \"UT_Interface\", \"enabled\": false, \"type\": \"iana-if-type:ethernetCsmacd\"}}" + t.Run("Test PATCH on interface type config", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interfaces type config ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"UT_Interface\", \"enabled\": false, \"mtu\": 8900, \"name\": \"Ethernet0\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" + t.Run("Test GET on interface type config", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interfaces wrong type ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"mtu\": 8900, \"description\": \"UT_Interface\", \"enabled\": false, \"type\": \"iana-if-type:ieee8023adLag\"}}" + wrong_type_err := errors.New("Unsupported interface type") + t.Run("Test PATCH on interface wrong type config", processSetRequest(url, url_input_body_json, "PATCH", true, wrong_type_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n+++++++++++++ Performing Delete on interfaces/interface[name=Ethernet0]/config/type node ++++++++++++") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/type" + del_not_supported_msg = "Delete operation not supported for this path - /openconfig-interfaces:interfaces/interface/config/type" + del_not_supported = tlerr.InvalidArgsError{Format: del_not_supported_msg} + t.Run("Test delete on interfaces/interface[name=Ethernet0]/config/type node", processDeleteRequest(url, true, del_not_supported)) + time.Sleep(1 * time.Second) + + t.Log("\n\n+++++++++++++ Validate interface/state node ++++++++++++") + pre_req_map = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet23": map[string]interface{}{"mtu": "9100"}}} + loadDB(db.ConfigDB, pre_req_map) + pre_req_map = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": map[string]interface{}{"description": "Test intf desc", "index": "100001", "oper_status": "up", "last_up_time": "Sat Feb 08 11:53:34 2025", "last_down_time": "Sat Feb 08 11:53:37 2025"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet23]/state" + expected_get_json = "{\"openconfig-interfaces:state\": {\"description\": \"Test intf desc\", \"ifindex\": 100001, \"oper-status\": \"UP\", \"last-change\": \"173901561700\", \"logical\": false, \"management\": false, \"cpu\": false, \"name\": \"Ethernet23\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" t.Run("Test GET on interface state", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": ""}} + unloadDB(db.ApplDB, cleanuptbl) + + t.Log("\n\n+++++++++++++ Validate interface/state/last-change only up-time in db node ++++++++++++") + pre_req_map = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": map[string]interface{}{"description": "Test intf desc", "index": "100001", "oper_status": "up", "last_up_time": "Sat Feb 08 11:53:34 2025"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet23]/state" + expected_get_json = "{\"openconfig-interfaces:state\": {\"description\": \"Test intf desc\", \"ifindex\": 100001, \"oper-status\": \"UP\", \"last-change\": \"173901561400\", \"logical\": false, \"management\": false, \"cpu\": false, \"name\": \"Ethernet23\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" + t.Run("Test GET on interface state/last-change only up-time in db node", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": ""}} + unloadDB(db.ApplDB, cleanuptbl) + t.Log("\n\n+++++++++++++ Validate interface/state/last-change only down-time in db node ++++++++++++") + pre_req_map = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": map[string]interface{}{"description": "Test intf desc", "index": "100001", "oper_status": "up", "last_down_time": "Sat Feb 08 11:53:34 2025"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet23]/state" + expected_get_json = "{\"openconfig-interfaces:state\": {\"description\": \"Test intf desc\", \"ifindex\": 100001, \"oper-status\": \"UP\", \"last-change\": \"173901561400\", \"logical\": false, \"management\": false, \"cpu\": false, \"name\": \"Ethernet23\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" + t.Run("Test GET on interface state/last-change only down-time in db node", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": ""}} unloadDB(db.ApplDB, cleanuptbl) + + t.Log("\n\n+++++++++++++ Validate interface/state/last-change both up-down same ++++++++++++") + pre_req_map = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": map[string]interface{}{"description": "Test intf desc", "index": "100001", "oper_status": "up", "last_up_time": "Sat Feb 08 11:53:34 2025", "last_down_time": "Sat Feb 08 11:53:34 2025"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet23]/state" + expected_get_json = "{\"openconfig-interfaces:state\": {\"description\": \"Test intf desc\", \"ifindex\": 100001, \"oper-status\": \"UP\", \"last-change\": \"173901561400\", \"logical\": false, \"management\": false, \"cpu\": false, \"name\": \"Ethernet23\", \"type\": \"iana-if-type:ethernetCsmacd\"}}" + t.Run("Test GET on interface state/last-change both up-down", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet23": ""}} + unloadDB(db.ApplDB, cleanuptbl) + + cleanuptbl = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet23": ""}} + unloadDB(db.ConfigDB, cleanuptbl) } func Test_openconfig_ethernet(t *testing.T) { @@ -167,7 +306,9 @@ func Test_openconfig_ethernet(t *testing.T) { t.Log("\n\n--- DELETE at ethernet container ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet" - t.Run("Test DELETE on ethernet", processDeleteRequest(url, true)) + del_err_str := "Delete operation not supported for this path - /openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet" + del_err := tlerr.InvalidArgsError{Format: del_err_str} + t.Run("Test DELETE on ethernet", processDeleteRequest(url, true, del_err)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at ethernet container ---") @@ -178,7 +319,7 @@ func Test_openconfig_ethernet(t *testing.T) { t.Log("\n\n--- DELETE at ethernet auto-negotiate ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/config/auto-negotiate" - t.Run("Test DELETE on ethernet auto-negotiate", processDeleteRequest(url, true)) + t.Run("Test DELETE on ethernet auto-negotiate", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at ethernet auto-negotiate ---") @@ -203,7 +344,9 @@ func Test_openconfig_ethernet(t *testing.T) { t.Log("\n\n--- DELETE at ethernet config ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/config" - t.Run("Test DELETE on ethernet config", processDeleteRequest(url, true)) + del_err_str = "Delete operation not supported for this path - /openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/config" + del_err = tlerr.InvalidArgsError{Format: del_err_str} + t.Run("Test DELETE on ethernet config", processDeleteRequest(url, true, del_err)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at ethernet config ---") @@ -217,7 +360,6 @@ func Test_openconfig_ethernet(t *testing.T) { expected_get_json = "{\"openconfig-if-ethernet:port-speed\": \"openconfig-if-ethernet:SPEED_10GB\"}" t.Run("Test GET on port-speed", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) - } func Test_openconfig_subintf_ipv4(t *testing.T) { @@ -226,7 +368,7 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Log("\n\n+++++++++++++ CONFIGURING AND REMOVING IPv4 ADDRESS AT SUBINTERFACES ++++++++++++") t.Log("\n\n--- TC 1: Delete/Clear existing IPv4 address on Ethernet0 ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" - t.Run("Test Delete/Clear IPv4 on subinterfaces", processDeleteRequest(url, true)) + t.Run("Test Delete/Clear IPv4 on subinterfaces", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Get/Verify IPv4 address at subinterfaces ---") @@ -250,7 +392,7 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Log("\n\n--- Delete IPv4 address at subinterfaces level ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces" - t.Run("Test Delete IPv4 address at subinterfaces level", processDeleteRequest(url, true)) + t.Run("Test Delete IPv4 address at subinterfaces level", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces level ---") @@ -288,7 +430,7 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Log("\n\n--- Delete IPv4 address at subinterfaces/subinterface[index=0] level ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]" - t.Run("Test Delete IPv4 address at subinterface[index=0] level", processDeleteRequest(url, true)) + t.Run("Test Delete IPv4 address at subinterface[index=0] level", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces/subinterface[index=0] level ---") @@ -318,23 +460,6 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Run("Test Get/Verify Patch IPv4 address at subinterfaces ipv4/addresses", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) - t.Log("\n\n--- Delete IPv4 address at addresses ---") - url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" - t.Run("Test Delete IPv4 address on subinterfaces addresses", processDeleteRequest(url, true)) - time.Sleep(1 * time.Second) - - t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces addresses level ---") - url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" - expected_get_json = "{}" - t.Run("Test Get/Verify Delete IPv4 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false)) - time.Sleep(1 * time.Second) - - t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces level ---") - url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces" - expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}}" - t.Run("Test Get/Verify Delete at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) - time.Sleep(1 * time.Second) - //------------------------------------------------------------------------------------------------------------------------------------ t.Log("\n\n--- Duplicate IP test: PATCH existing IPv4 address on another interface ---") @@ -360,7 +485,7 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Log("\n\n--- Delete IPv4 address ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses/address[ip=4.4.4.4]" - t.Run("Test Delete IPv4 address on subinterfaces address", processDeleteRequest(url, true)) + t.Run("Test Delete IPv4 address on subinterfaces address", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces level ---") @@ -369,6 +494,36 @@ func Test_openconfig_subintf_ipv4(t *testing.T) { t.Run("Test Get/Verify Delete IPv4 address at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"4.4.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"4.4.4.4\", \"prefix-length\": 24}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + expected_get_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"config\": {\"ip\": \"4.4.4.4\", \"prefix-length\": 24}, \"ip\": \"4.4.4.4\"}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces ipv4/addresses", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv4 address at addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + t.Run("Test Delete IPv4 address on subinterfaces addresses", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + expected_get_json = "{}" + t.Run("Test Get/Verify Delete IPv4 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Delete at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ DONE CONFIGURING AND REMOVING IPV4 ADDRESSES ON SUBINTERFACES ++++++++++++") } @@ -378,7 +533,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Log("\n\n+++++++++++++ CONFIGURING AND REMOVING IPv6 ADDRESS AT SUBINTERFACES ++++++++++++") t.Log("\n\n--- Delete/Clear IPv6 address ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" - t.Run("Test Delete/Clear IPv6 address on subinterfaces addresses", processDeleteRequest(url, true)) + t.Run("Test Delete/Clear IPv6 address on subinterfaces addresses", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Get IPv6 address at subinterfaces ---") @@ -400,9 +555,27 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Run("Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- Verify IPv6 address at subinterfaces with address key ---") + pre_req_map := map[string]interface{}{"PORT": map[string]interface{}{"Ethernet77": map[string]interface{}{"mtu": "9100"}}} + loadDB(db.ConfigDB, pre_req_map) + pre_req_map = map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet77:a::e/64": map[string]interface{}{"NULL": "NULL"}}} + loadDB(db.ApplDB, pre_req_map) + pre_req_map = map[string]interface{}{"INTERFACE": map[string]interface{}{"Ethernet77|a::e/64": map[string]interface{}{"NULL": "NULL"}}} + loadDB(db.ConfigDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet77]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses/address[ip=a::e]" + expected_get_json = "{\"openconfig-if-ip:address\":[{\"config\":{\"ip\":\"a::e\",\"prefix-length\":64},\"ip\":\"a::e\", \"state\":{\"ip\":\"a::e\",\"prefix-length\":64}}]}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces with address key", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl := map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet77:a::e/64": ""}} + unloadDB(db.ApplDB, cleanuptbl) + cleanuptbl = map[string]interface{}{"INTERFACE": map[string]interface{}{"Ethernet77|a::e/64": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + cleanuptbl = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet77": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + t.Log("\n\n--- Delete IPv6 address at subinterfaces ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces" - t.Run("Test Delete IPv6 address at subinterfaces", processDeleteRequest(url, true)) + t.Run("Test Delete IPv6 address at subinterfaces", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces ---") @@ -417,7 +590,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"a::e\", \"openconfig-if-ip:config\": {\"ip\": \"a::e\", \"prefix-length\": 64}}]}}" time.Sleep(1 * time.Second) - t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + t.Run(" Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify PATCH IPv6 address at subinterface level ---") @@ -428,7 +601,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Log("\n\n--- Delete IPv6 address at subinterface ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface" - t.Run("Test Delete IPv6 address at subinterface", processDeleteRequest(url, true)) + t.Run("Test Delete IPv6 address at subinterface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv6 address at subinterface ---") @@ -443,7 +616,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"a::e\", \"openconfig-if-ip:config\": {\"ip\": \"a::e\", \"prefix-length\": 64}}]}}" time.Sleep(1 * time.Second) - t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + t.Run(" Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify PATCH IPv6 address at addresses level ---") @@ -454,7 +627,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Log("\n\n--- Delete IPv6 address at subinterfaces addresses level---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" - t.Run("Test Delete IPv6 address at subinterfaces addresses level", processDeleteRequest(url, true)) + t.Run("Test Delete IPv6 address at subinterfaces addresses level", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces addresses ---") @@ -477,7 +650,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Log("\n\n--- Delete IPv6 address ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses/address[ip=a::e]" - t.Run("Test Delete/Clear IPv6 address on subinterfaces address", processDeleteRequest(url, true)) + t.Run("Test Delete/Clear IPv6 address on subinterfaces address", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) //------------------------------------------------------------------------------------------------------------------------------------ @@ -498,6 +671,34 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Run("Test Get IPv6 address at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH IPv6 address at addresses format test ---") + pre_req_map = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet22": map[string]interface{}{"mtu": "9100"}}} + loadDB(db.ConfigDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet22]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\", \"openconfig-if-ip:config\": {\"ip\": \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run(" Test Patch/Set IPv6 address on subinterfaces addresses format test", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet22]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2001:db8:85a3::8a2e:370:7334\",\"prefix-length\":64},\"ip\":\"2001:db8:85a3::8a2e:370:7334\"}]},\"config\":{\"enabled\":false}, \"state\": {\"enabled\": false}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces level format test", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces with address key format test ---") + pre_req_map = map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet22:2001:db8:85a3::8a2e:370:7334/64": map[string]interface{}{"NULL": "NULL"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet22]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses/address[ip=2001:0db8:85a3:0000:0000:8a2e:0370:7334]" + expected_get_json = "{\"openconfig-if-ip:address\":[{\"config\":{\"ip\":\"2001:db8:85a3::8a2e:370:7334\",\"prefix-length\":64},\"ip\":\"2001:db8:85a3::8a2e:370:7334\", \"state\":{\"ip\":\"2001:db8:85a3::8a2e:370:7334\",\"prefix-length\":64}}]}" + //t.Run("Test Get/Verify Patch IPv6 address at subinterfaces with address key format test", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet22:2001:db8:85a3::8a2e:370:7334/64": ""}} + unloadDB(db.ApplDB, cleanuptbl) + cleanuptbl = map[string]interface{}{"INTERFACE": map[string]interface{}{"Ethernet22|2001:db8:85a3::8a2e:370:7334/64": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + cleanuptbl = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet22": ""}} + unloadDB(db.ConfigDB, cleanuptbl) t.Log("\n\n+++++++++++++ DONE CONFIGURING AND REMOVING IPV6 ADDRESSES ON SUBINTERFACES ++++++++++++") t.Log("\n\n+++++++++++++ ENABLE AND DISABLE IPV6 LINK LOCAL ON SUBINTERFACES ++++++++++++") @@ -523,7 +724,7 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Log("\n\n--- Delete/Disable IPv6 link local ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/ipv6/config/enabled" - t.Run("Test Delete/Disable IPv6 link local on subinterfaces config", processDeleteRequest(url, true)) + t.Run("Test Delete/Disable IPv6 link local on subinterfaces config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify Delete IPv6 link local ---") @@ -532,5 +733,19 @@ func Test_openconfig_subintf_ipv6(t *testing.T) { t.Run("Test Get/Verify Delete IPv6 link local at subinterfaces config level", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- Verify Get ipv6 enabled at state level ---") + pre_req_map = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet99": map[string]interface{}{"mtu": "9100"}}} + loadDB(db.ConfigDB, pre_req_map) + pre_req_map = map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet99": map[string]interface{}{"ipv6_use_link_local_only": "enable"}}} + loadDB(db.ApplDB, pre_req_map) + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet99]/subinterfaces/subinterface[index=0]/ipv6/state" + expected_get_json = "{\"openconfig-if-ip:state\": {\"enabled\": true}}" + t.Run("Test GET on interface ipv6 state/enabled", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Ethernet99": ""}} + unloadDB(db.ApplDB, cleanuptbl) + cleanuptbl = map[string]interface{}{"PORT": map[string]interface{}{"Ethernet99": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + t.Log("\n\n+++++++++++++ DONE ENABLING AND DISABLING IPV6 LINK LOCAL ON SUBINTERFACES ++++++++++++") } diff --git a/translib/transformer/loopback_openconfig_test.go b/translib/transformer/loopback_openconfig_test.go new file mode 100644 index 000000000..448778248 --- /dev/null +++ b/translib/transformer/loopback_openconfig_test.go @@ -0,0 +1,501 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2025 Cisco. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//go:build testapp +// +build testapp + +package transformer_test + +import ( + "errors" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "testing" + "time" +) + +func Test_openconfig_loopback_interfaces(t *testing.T) { + var url, url_input_body_json string + + t.Log("\n\n+++++++++++++ CONFIGURING LOOPBACK ++++++++++++") + + t.Log("\n\n--- POST to Create Loopback11 ---") + url = "/openconfig-interfaces:interfaces" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback11\", \"config\": {\"name\": \"Loopback11\"}}]}" + t.Run("Test Create Loopback11", processSetRequest(url, url_input_body_json, "POST", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Loopback Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/config" + expected_get_json := "{\"openconfig-interfaces:config\": {\"name\": \"Loopback11\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}}" + t.Run("Test GET Loopback interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PUT to Create Loopback22 ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback22\", \"config\": {\"name\": \"Loopback22\"}}]}" + t.Run("Test Create Loopback22", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Loopback Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"name\": \"Loopback22\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}}" + t.Run("Test GET Loopback interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n+++++++++++++ CONFIGURING INTERFACES ATTRIBUTES ++++++++++++") + t.Log("\n\n--- PATCH interface leaf nodes---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/config/mtu" + url_input_body_json = "{\"openconfig-interfaces:mtu\": 9000}" + err_mtu_str := "Configuration for MTU is not supported for Loopback interface " + var expected_mtu_err error = errors.New(err_mtu_str) + t.Run("Test PATCH on interface mtu", processSetRequest(url, url_input_body_json, "PATCH", true, expected_mtu_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PUT to Create Loopback100 ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback100\", \"config\": {\"name\": \"Loopback100\"}}]}" + t.Run("Test PUT with Loopback100", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + pre_req_map := map[string]interface{}{"INTF_TABLE": map[string]interface{}{"Loopback100": map[string]interface{}{"NULL": "NULL"}}} + loadDB(db.ApplDB, pre_req_map) + + t.Log("\n\n--- Verify Loopback Creation Loockback100 --") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]" + expected_get_json = "{\"openconfig-interfaces:interface\":[{ \"config\": {\"name\": \"Loopback100\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}, \"state\": {\"name\": \"Loopback100\",\"cpu\": false,\"logical\": true,\"management\": false,\"type\": \"iana-if-type:softwareLoopback\"}, \"name\": \"Loopback100\", \"subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"state\": {\"index\": 0}}]}}]}" + t.Run(" Test GET Loopback100 interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interface leaf enabled node ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/enabled" + url_input_body_json = "{\"openconfig-interfaces:enabled\": true}" + t.Run("Test PATCH on interface enabled", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interface leaf enabled node ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/enabled" + expected_get_json = "{\"openconfig-interfaces:enabled\": true}" + t.Run("Test GET on interface PATCH enabled config", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- DELETE at interface enabled ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/enabled" + disable_err := errors.New("Disabling Loopback port is not supported") + t.Run("Test DELETE on interface enabled", processDeleteRequest(url, true, disable_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at interface enabled ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/enabled" + expected_get_json = "{\"openconfig-interfaces:enabled\": true}" + t.Run("Test GET on interface enabled delete config", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interface leaf enabled node false ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/enabled" + url_input_body_json = "{\"openconfig-interfaces:enabled\": false}" + t.Run("Test PATCH on interface enabled", processSetRequest(url, url_input_body_json, "PATCH", true, disable_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- GET interface desc before patch ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/description" + err_str := "Resource not found" + expected_err_invalid := tlerr.NotFoundError{Format: err_str} + expected_get_json = "{}" + t.Run("Test GET on interface config", processGetRequest(url, nil, expected_get_json, true, expected_err_invalid)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interfaces desc ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"description\": \"UT_Loopback_Interface\", \"enabled\": true }}" + t.Run("Test PATCH on interface description config", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interfaces desc config ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"name\": \"Loopback100\", \"type\": \"iana-if-type:softwareLoopback\", \"description\": \"UT_Loopback_Interface\"}}" + //t.Run("Test GET on interface desc config", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- DELETE at interface desc ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/description" + t.Run("Test DELETE on interface desc", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at interface desc ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback100]/config/description" + err_str = "Resource not found" + expected_err_invalid = tlerr.NotFoundError{Format: err_str} + expected_get_json = "{}" + t.Run("Test GET on interface config", processGetRequest(url, nil, expected_get_json, true, expected_err_invalid)) + time.Sleep(1 * time.Second) + + cleanuptbl := map[string]interface{}{"PORT_TABLE": map[string]interface{}{"Ethernet0": ""}} + unloadDB(db.ApplDB, cleanuptbl) + + //----------- + + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"24.4.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"24.4.4.4\", \"prefix-length\": 24}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"24.4.4.4\", \"prefix-length\": 24}, \"ip\": \"24.4.4.4\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //patch to update existing value (changing prefix length of ip) + t.Log("\n\n--- PATCH IPv4 Prefix length address at addresses level from 24 to 32 ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"24.4.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"24.4.4.4\", \"prefix-length\": 32}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"24.4.4.4\", \"prefix-length\": 32}, \"ip\": \"24.4.4.4\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //patch to add new ip || for Ipv4 currently only one IP is allowed per interface + //Primary IP config already happened and replacing it with new one + + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"56.40.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"56.40.4.4\", \"prefix-length\": 32}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"56.40.4.4\", \"prefix-length\": 32}, \"ip\": \"56.40.4.4\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Put IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"44.40.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"44.40.4.4\", \"prefix-length\": 32}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Put/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"44.40.4.4\", \"prefix-length\": 32}, \"ip\": \"44.40.4.4\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run(" Verify Put IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //Test conflicting IP (same subnet) + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"44.40.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"44.40.4.4\", \"prefix-length\": 32}}]}}" + + err_str = "IP 44.40.4.4/32 overlaps with IP or IP Anycast 44.40.4.4/32 of Interface Loopback11" + expected_err1 := tlerr.InvalidArgsError{Format: err_str} + + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on another interface", processSetRequest(url, url_input_body_json, "PATCH", true, expected_err1)) + time.Sleep(1 * time.Second) + + //-------------- + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"34.4.4.4\", \"openconfig-if-ip:config\": {\"ip\": \"34.4.4.4\", \"prefix-length\": 24}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"34.4.4.4\", \"prefix-length\": 24}, \"ip\": \"34.4.4.4\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- DELETE at interfaces/interface container Loopback22 interface---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]" + t.Run("Test DELETE on interface container", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at Loopback22 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback22]" + err_str = "Resource not found" + expected_get_json = "{}" + expected_err := tlerr.NotFoundError{Format: err_str} + t.Run("Test GET on deleted Loopback interface", processGetRequest(url, nil, expected_get_json, true, expected_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Test ethernet container ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/openconfig-if-ethernet:ethernet/config/port-speed" + url_input_body_json = "{\"openconfig-if-ethernet:port-speed\":\"SPEED_40GB\"}" + err_str = "Error: Unsupported Interface: Loopback11" + invalid_port_err := errors.New(err_str) + t.Run("Test ethernet container", processSetRequest(url, url_input_body_json, "PATCH", true, invalid_port_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Test aggregate container ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]/openconfig-if-aggregate:aggregation/config/min-links" + url_input_body_json = "{\"openconfig-if-aggregator:min-links\": 2}" + err_str = "Container not supported for given interface type" + invalid_port_err = errors.New(err_str) + t.Run("Test aggregator container", processSetRequest(url, url_input_body_json, "PATCH", true, invalid_port_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- DELETE at interfaces/interface container Loopback11 interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]" + t.Run("Test DELETE on interface container", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at Loopback11 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback11]" + err_str = "Resource not found" + expected_err = tlerr.NotFoundError{Format: err_str} + t.Run("Test GET on deleted Loopback interface", processGetRequest(url, nil, "", true, expected_err)) + time.Sleep(1 * time.Second) +} + +func Test_openconfig_loopback_ipv6_ipv4_addresses(t *testing.T) { + t.Log("\n\n+++++++++++++ CONFIGURING LOOPBACK ++++++++++++") + + t.Log("\n\n--- PUT to Create Loopback14 ---") + url := "/openconfig-interfaces:interfaces/interface[name=Loopback14]" + url_input_body_json := "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback14\", \"config\": {\"name\": \"Loopback14\"}}]}" + t.Run("Test Create Loopback14", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Loopback Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/config" + expected_get_json := "{\"openconfig-interfaces:config\": {\"name\": \"Loopback14\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}}" + t.Run("Test GET Loopback interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PUT to Create Loopback15 ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback15\", \"config\": {\"name\": \"Loopback15\"}}]}" + t.Run("Test Create Loopback15", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Loopback Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"name\": \"Loopback15\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}}" + t.Run("Test GET Loopback interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PUT to Create Loopback21 ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"Loopback21\", \"config\": {\"name\": \"Loopback21\"}}]}" + t.Run("Test Create Loopback21", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Loopback Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"name\": \"Loopback21\", \"enabled\": true, \"type\": \"iana-if-type:softwareLoopback\"}}" + t.Run("Test GET Loopback interface creation config ", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n-- PATCH IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2000::24\", \"openconfig-if-ip:config\": {\"ip\": \"2000::24\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2000::24\",\"prefix-length\":64},\"ip\":\"2000::24\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //Test conflicting IP (same subnet) + t.Log("\n\n---PATCH IPv6 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2000::24\", \"openconfig-if-ip:config\": {\"ip\": \"2000::24\", \"prefix-length\": 64}}]}}" + err_str := "IP 2000::24/64 overlaps with IP or IP Anycast 2000::24/64 of Interface Loopback21" + expected_err1 := tlerr.InvalidArgsError{Format: err_str} + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on another interface", processSetRequest(url, url_input_body_json, "PATCH", true, expected_err1)) + time.Sleep(1 * time.Second) + + // patch with same ip (updating existing address) + t.Log("\n\n--- PATCH IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2000::24\", \"openconfig-if-ip:config\": {\"ip\": \"2000::24\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2000::24\",\"prefix-length\":64},\"ip\":\"2000::24\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run(" Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + // patch with new ip (adding new address) + t.Log("\n\n--- PATCH IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"8000::42\", \"openconfig-if-ip:config\": {\"ip\": \"8000::42\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2000::24\",\"prefix-length\":64},\"ip\":\"2000::24\"},{\"config\":{\"ip\":\"8000::42\",\"prefix-length\":64},\"ip\":\"8000::42\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run(" Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //----------- + + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"74.74.74.74\", \"openconfig-if-ip:config\": {\"ip\": \"74.74.74.74\", \"prefix-length\": 24}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"74.74.74.74\", \"prefix-length\": 24}, \"ip\": \"74.74.74.74\"}]}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2004::2\", \"openconfig-if-ip:config\": {\"ip\": \"2004::2\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2004::2\",\"prefix-length\":64},\"ip\":\"2004::2\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + + time.Sleep(1 * time.Second) + + //***currently for ipv6, replace operation doesn't remove Old Data, it just Appends the new data + //curr [a], put [b], ideally we should have [b], we have [a,b] + t.Log("\n\n--- PUT IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2006::8\", \"openconfig-if-ip:config\": {\"ip\": \"2006::8\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Put/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n---Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2004::2\",\"prefix-length\":64},\"ip\":\"2004::2\"},{\"config\":{\"ip\":\"2006::8\",\"prefix-length\":64},\"ip\":\"2006::8\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify put IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //we have [a,b], PUT [a_update], ideally we should have only [a_update] + //but we have [a_update, b] + t.Log("\n\n--- PUT IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2004::2\", \"openconfig-if-ip:config\": {\"ip\": \"2004::2\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Put/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2004::2\",\"prefix-length\":64},\"ip\":\"2004::2\"},{\"config\":{\"ip\":\"2006::8\",\"prefix-length\":64},\"ip\":\"2006::8\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify put IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + //------------------------------------------------------------------------------------------------------------------------------------ + err_str = "Resource not found" + expected_err := tlerr.NotFoundError{Format: err_str} + + t.Log("\n\n+++++++++++++ REMOVING IPv4 ADDRESS AT SUBINTERFACES LOOPBACK INTERFACE ++++++++++++") + t.Log("\n\n--- Delete/Clear existing IPv4 address on Loopback Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + t.Run("Test Delete/Clear IPv4 on subinterfaces", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Get/Verify IPv4 address at subinterfaces ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback14]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4" + expected_get_json = "{}" + t.Run("Test Get/Verify Delete IPv4 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv6 address at subinterfaces addresses level---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + t.Run("Test Delete IPv6 address at subinterfaces addresses level", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]/subinterfaces/subinterface[index=0]/ipv6/addresses" + expected_get_json = "{}" + t.Run("Test Get/Verify Delete IPv6 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n---Delete existing IPv6 address on Loopback Interface at subinterfaces addresses address level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses/address[ip=8000::42]" + t.Run("Test Delete IPv6 on subinterfaces addresses address[x]", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify delete IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2000::24\",\"prefix-length\":64},\"ip\":\"2000::24\"}]}}, \"state\":{\"index\":0}}]}}" + t.Run(" Test Get/Verify delete IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv6 address at subinterfaces addresses level---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + t.Run("Test Delete IPv6 address at subinterfaces addresses level", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]/subinterfaces/subinterface[index=0]/ipv6/addresses" + expected_get_json = "{}" + t.Run(" Test Get/Verify Delete IPv6 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false, nil)) + time.Sleep(1 * time.Second) + + //------ + + t.Log("\n\n--- DELETE at Loopback21 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]" + t.Run(" Test DELETE loopback interface", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at Loopback21 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback21]" + err_str = "Resource not found" + expected_err = tlerr.NotFoundError{Format: err_str} + expected_get_json = "{}" + t.Run("Test GET on deleted Loopback interface", processGetRequest(url, nil, expected_get_json, true, expected_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- DELETE at Loopback15 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]" + t.Run("Test DELETE Loopback interface", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify DELETE at Loopback15 Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=Loopback15]" + err_str = "Resource not found" + expected_err = tlerr.NotFoundError{Format: err_str} + expected_get_json = "{}" + t.Run("Test GET on deleted Loopback interface", processGetRequest(url, nil, expected_get_json, true, expected_err)) + time.Sleep(1 * time.Second) +} diff --git a/translib/transformer/portchannel_openconfig_test.go b/translib/transformer/portchannel_openconfig_test.go index da122d922..25e5590d2 100644 --- a/translib/transformer/portchannel_openconfig_test.go +++ b/translib/transformer/portchannel_openconfig_test.go @@ -22,6 +22,8 @@ package transformer_test import ( + "errors" + "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/tlerr" "testing" "time" @@ -40,14 +42,51 @@ func Test_openconfig_portchannel(t *testing.T) { t.Log("\n\n--- Verify PortChannel Creation ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/config" - expected_get_json := "{\"openconfig-interfaces:config\": {\"description\": \"put_pc\", \"enabled\": true, \"mtu\": 9100, \"name\": \"PortChannel111\"}}" + expected_get_json := "{\"openconfig-interfaces:config\": {\"description\": \"put_pc\", \"enabled\": true, \"mtu\": 9100, \"name\": \"PortChannel111\", \"type\": \"iana-if-type:ieee8023adLag\"}}" t.Run("Test GET PortChannel interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH IPv4 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"64.64.64.64\", \"openconfig-if-ip:config\": {\"ip\": \"64.64.64.64\", \"prefix-length\": 24}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv4 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv4 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv4\": {\"addresses\": {\"address\": [{\"config\": {\"ip\": \"64.64.64.64\", \"prefix-length\": 24}, \"ip\": \"64.64.64.64\"}]}}, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Patch IPv4 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PUT to Replace/Create PortChannel 123 ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel123]" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\": \"PortChannel123\", \"config\": {\"name\": \"PortChannel123\", \"mtu\": 9200, \"description\": \"put_pc_updated\", \"enabled\": true}}]}" + t.Run("Test PUT PortChannel123", processSetRequest(url, url_input_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PortChannel Replacement/Creation ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel123]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"put_pc_updated\", \"enabled\": true, \"mtu\": 9200, \"name\": \"PortChannel123\", \"type\": \"iana-if-type:ieee8023adLag\"}}" + t.Run("Test GET PortChannel interface after PUT", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + t.Log("\n\n--- Initialize PortChannel Member ---") t.Log("\n\n--- DELETE interface IP Addr ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" - t.Run("DELETE on interface IP Addr", processDeleteRequest(url, true)) + t.Run("DELETE on interface IP Addr", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n+++++++++++++ CONFIGURING AND REMOVING IPv4 ADDRESS AT SUBINTERFACES PORTCHANNEL INTERFACE ++++++++++++") + t.Log("\n\n--- Delete/Clear existing IPv4 address on PortChannel Interface ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses" + t.Run("Test Delete/Clear IPv4 on subinterfaces", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Get/Verify IPv4 address at subinterfaces ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get IPv4 address at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- PATCH to Add PortChannel Member ---") @@ -62,6 +101,70 @@ func Test_openconfig_portchannel(t *testing.T) { t.Run("Test GET on portchannel agg-id", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + //Verify adding new agg-id on same port + t.Log("\n\n--- Verify adding new agg-id on same port ---") + url = "/openconfig-interfaces:interfaces" + url_input_body_json = "{\"openconfig-interfaces:interface\": [{\"name\":\"PortChannel222\", \"config\": {\"name\": \"PortChannel222\", \"mtu\": 9100, \"description\": \"put_pc\", \"enabled\": true}}]}" + t.Run("Test Create PortChannel222", processSetRequest(url, url_input_body_json, "POST", false, nil)) + time.Sleep(1 * time.Second) + + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + url_input_body_json = "{\"openconfig-if-aggregate:aggregate-id\":\"PortChannel222\"}" + agg_exist_err := tlerr.InvalidArgsError{Format: "Ethernet0 Interface is already member of PortChannel111"} + t.Run("Test PATCH on Ethernet aggregate-id error case", processSetRequest(url, url_input_body_json, "PATCH", true, agg_exist_err)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH to re-Add PortChannel Member ---") + url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + url_input_body_json = "{\"openconfig-if-aggregate:aggregate-id\":\"PortChannel111\"}" + t.Run("Test PATCH on Ethernet aggregate-id re-add", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interfaces type ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"description\": \"UT_Interface\", \"enabled\": false, \"type\": \"iana-if-type:ieee8023adLag\"}}" + t.Run("Test PATCH on interface type config", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH interfaces type config ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/config" + expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"UT_Interface\", \"enabled\": false, \"name\": \"PortChannel111\", \"type\": \"iana-if-type:ieee8023adLag\", \"mtu\": 9100}}" + t.Run("Test GET on interface type config", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- PATCH interfaces wrong type ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/config" + url_input_body_json = "{\"openconfig-interfaces:config\": { \"description\": \"UT_Interface\", \"enabled\": false, \"type\": \"iana-if-type:softwareLoopback\"}}" + wrong_type_err := errors.New("Unsupported interface type") + t.Run("Test PATCH on interface wrong type config", processSetRequest(url, url_input_body_json, "PATCH", true, wrong_type_err)) + time.Sleep(1 * time.Second) + + pre_req_map := map[string]interface{}{"LAG_TABLE": map[string]interface{}{"PortChannel111": map[string]interface{}{"description": "UT-Po-Port", "admin_status": "up", "index": "100001", "oper_status": "up", "last_up_time": "Sat Feb 08 11:53:34 2025", "last_down_time": "Sat Feb 08 11:53:37 2025", "mtu": "8888"}}} + loadDB(db.ApplDB, pre_req_map) + + t.Log("\n\n--- Verify interface state leaf nodes ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/state" + expected_get_json = "{\"openconfig-interfaces:state\": {\"logical\": true, \"management\": false, \"cpu\": false, \"type\": \"iana-if-type:ieee8023adLag\", \"description\": \"UT-Po-Port\", \"ifindex\": 100001, \"oper-status\": \"UP\", \"last-change\": \"173901561700\", \"name\": \"PortChannel111\", \"mtu\": 8888, \"admin-status\": \"UP\", \"enabled\": true}}" + t.Run("Test GET on interface state", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + cleanuptbl := map[string]interface{}{"LAG_TABLE": map[string]interface{}{"PortChannel111": ""}} + unloadDB(db.ApplDB, cleanuptbl) + + t.Log("\n\n+++++++++++++ CONFIGURING ETHERNET ATTRIBUTES ++++++++++++") + t.Log("\n\n--- PATCH ethernet auto-neg and port-speed ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/openconfig-if-ethernet:ethernet/config/port-speed" + url_input_body_json = "{\"openconfig-if-ethernet:port-speed\":\"SPEED_40GB\"}" + speed_err := errors.New("Speed config not supported for given Interface type") + t.Run("Test PATCH on ethernet port-speed", processSetRequest(url, url_input_body_json, "PATCH", true, speed_err)) + time.Sleep(1 * time.Second) + + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/openconfig-if-ethernet:ethernet/config/auto-negotiate" + url_input_body_json = "{\"openconfig-if-ethernet:auto-negotiate\":true}" + auto_neg_err := errors.New("AutoNegotiate config not supported for given Interface type") + t.Run("Test PATCH on ethernet auto-neg", processSetRequest(url, url_input_body_json, "PATCH", true, auto_neg_err)) + time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH PortChannel min-links ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/openconfig-if-aggregate:aggregation/config/min-links" url_input_body_json = "{\"openconfig-if-aggregate:min-links\":3}" @@ -76,12 +179,12 @@ func Test_openconfig_portchannel(t *testing.T) { t.Log("\n\n--- DELETE PortChannel min-links ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/openconfig-if-aggregate:aggregation/config/min-links" - t.Run("Verify DELETE on PortChannel min-links", processDeleteRequest(url, true)) + t.Run("Verify DELETE on PortChannel min-links", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE PortChannel min-links ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/openconfig-if-aggregate:aggregation/config" - expected_get_json = "{\"openconfig-if-aggregate:config\": {\"min-links\": 3}}" + expected_get_json = "{\"openconfig-if-aggregate:config\": {\"min-links\": 1}}" t.Run("Test GET on portchannel min-links after DELETE", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -93,13 +196,91 @@ func Test_openconfig_portchannel(t *testing.T) { t.Log("\n\n--- Verify PATCH interfaces config ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"agg_intf_conf\", \"enabled\": false, \"mtu\": 8900, \"name\": \"PortChannel111\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"description\": \"agg_intf_conf\", \"enabled\": false, \"mtu\": 8900, \"name\": \"PortChannel111\", \"type\": \"iana-if-type:ieee8023adLag\"}}" t.Run("Test GET PortChannel interface Config", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) + t.Log("\n\n--- PATCH IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"f::f\", \"openconfig-if-ip:config\": {\"ip\": \"f::f\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify IPv6 address at subinterfaces level ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"f::f\",\"prefix-length\":64},\"ip\":\"f::f\"}]},\"config\":{\"enabled\":false}, \"state\": {\"enabled\": false}}, \"state\":{\"index\":0}}]}}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces level", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv6 address at subinterfaces ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces" + t.Run("Test Delete IPv6 address at subinterfaces", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces" + expected_get_json = "{\"openconfig-interfaces:subinterfaces\": {\"subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}}" + t.Run("Test Get/Verify Delete IPv6 address at subinterfaces", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //------------------------------------------------------------------------------------------------------------------------------------ + + t.Log("\n\n--- PATCH IPv6 address at addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"b::e\", \"openconfig-if-ip:config\": {\"ip\": \"b::e\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH IPv6 address at subinterface level ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface" + expected_get_json = "{\"openconfig-interfaces:subinterface\": [{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"b::e\",\"prefix-length\":64},\"ip\":\"b::e\"}]},\"config\":{\"enabled\":false}, \"state\": {\"enabled\": false}}, \"state\":{\"index\":0}}]}" + t.Run("Test Get/Verify Patch IPv6 address at subinterface", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv6 address at subinterface ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface" + t.Run("Test Delete IPv6 address at subinterface", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv6 address at subinterface ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface" + expected_get_json = "{\"openconfig-interfaces:subinterface\": [{\"config\": {\"index\": 0}, \"index\": 0, \"openconfig-if-ip:ipv6\": {\"config\": {\"enabled\": false}, \"state\": {\"enabled\": false}}, \"state\": {\"index\": 0}}]}" + t.Run("Test Get/Verify Delete IPv6 address at subinterface", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //------------------------------------------------------------------------------------------------------------------------------------ + + t.Log("\n\n--- PATCH IPv6 address at addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + url_input_body_json = "{\"openconfig-if-ip:addresses\": {\"address\": [{\"ip\": \"2001::e\", \"openconfig-if-ip:config\": {\"ip\": \"2001::e\", \"prefix-length\": 64}}]}}" + time.Sleep(1 * time.Second) + t.Run("Test Patch/Set IPv6 address on subinterfaces addresses", processSetRequest(url, url_input_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify PATCH IPv6 address at addresses level ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + expected_get_json = "{\"openconfig-if-ip:addresses\":{\"address\":[{\"config\":{\"ip\":\"2001::e\",\"prefix-length\":64},\"ip\":\"2001::e\"}]}}" + t.Run("Test Get/Verify Patch IPv6 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Delete IPv6 address at subinterfaces addresses level---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv6/addresses" + t.Run("Test Delete IPv6 address at subinterfaces addresses level", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + t.Log("\n\n--- Verify Delete IPv6 address at subinterfaces addresses ---") + url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]/subinterfaces/subinterface[index=0]/ipv6/addresses" + expected_get_json = "{}" + t.Run("Test Get/Verify Delete IPv6 address at subinterfaces addresses", processGetRequest(url, nil, expected_get_json, false)) + time.Sleep(1 * time.Second) + + //------------------------------------------------------------------------------------------------------------------------------------ + t.Log("\n\n--- DELETE PortChannel interface ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel111]" - t.Run("Test DELETE on PortChannel", processDeleteRequest(url, true)) + t.Run("Test DELETE on PortChannel", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify DELETE at PortChannel Interface ---") diff --git a/translib/transformer/sw_portchannel.go b/translib/transformer/sw_portchannel.go index bfec954c3..5fff410e8 100644 --- a/translib/transformer/sw_portchannel.go +++ b/translib/transformer/sw_portchannel.go @@ -245,6 +245,13 @@ var YangToDb_lag_min_links_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (m return res_map, err } + intfType, _, err := getIntfTypeByName(ifKey) + if intfType != IntfTypePortChannel || err != nil { + errStr := "Invalid interface type: " + ifKey + err = tlerr.InvalidArgsError{Format: errStr} + return res_map, err + } + minLinks, _ := inParams.param.(*uint16) if int(*minLinks) > 32 || int(*minLinks) < 0 { diff --git a/translib/transformer/utils_test.go b/translib/transformer/utils_test.go index e54193a6c..455915eec 100644 --- a/translib/transformer/utils_test.go +++ b/translib/transformer/utils_test.go @@ -67,6 +67,10 @@ func processGetRequest(url string, qparams *queryParamsUT, expectedRespJson stri checkErr(t, err, expErr[0]) } return + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) + return } err = json.Unmarshal([]byte(expectedRespJson), &expectedMap) @@ -108,6 +112,10 @@ func processGetRequestWithFile(url string, expectedJsonFile string, errorCase bo checkErr(t, err, expErr[0]) } return + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) + return } respJson := response.Payload @@ -141,6 +149,9 @@ func processSetRequest(url string, jsonPayload string, oper string, errorCase bo } else if expErr != nil { checkErr(t, err, expErr[0]) } + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) } } } @@ -167,6 +178,9 @@ func processSetRequestFromFile(url string, jsonFile string, oper string, errorCa } else if expErr != nil { checkErr(t, err, expErr[0]) } + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) } } } @@ -180,6 +194,9 @@ func processDeleteRequest(url string, errorCase bool, expErr ...error) func(*tes } else if expErr != nil { checkErr(t, err, expErr[0]) } + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) } } } @@ -200,6 +217,9 @@ func processActionRequest(url string, jsonPayload string, oper string, user stri } else if expErr != nil { checkErr(t, err, expErr[0]) } + } else if errorCase { + // Testcase expected an error, but no error recvd + t.Fatalf("Error expected but no error received for Url: %s", url) } } } diff --git a/translib/transformer/vlan_openconfig_test.go b/translib/transformer/vlan_openconfig_test.go index e6b3b9666..ff507090f 100644 --- a/translib/transformer/vlan_openconfig_test.go +++ b/translib/transformer/vlan_openconfig_test.go @@ -41,7 +41,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN Creation (PATCH) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config" - expected_get_json := "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan10\"}}" + expected_get_json := "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan10\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -53,19 +53,19 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN Creations (PATCH) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan20]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan20\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan20\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify VLAN Creations (PATCH) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan30\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan30\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify VLAN Creations (PATCH) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan40]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan40\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan40\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -77,7 +77,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN Creation (POST) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan50]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan50\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan50\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -89,19 +89,19 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN Creations (POST) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan60]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan60\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan60\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify VLAN Creations (POST) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan70]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan70\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan70\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify VLAN Creations (POST) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan80]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan80\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan80\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -109,7 +109,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- GET VLAN (interface level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]" - expected_get_json = "{\"openconfig-interfaces:interface\":[{\"config\":{\"enabled\":true,\"mtu\":9000,\"name\":\"Vlan10\"},\"name\":\"Vlan10\",\"openconfig-vlan:routed-vlan\":{\"openconfig-if-ip:ipv6\":{\"config\":{\"enabled\":false},\"state\":{\"enabled\":false}}},\"state\":{\"name\":\"Vlan10\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"config\":{\"enabled\":false},\"state\":{\"enabled\":false}},\"state\":{\"index\":0}}]}}]}" + expected_get_json = "{\"openconfig-interfaces:interface\":[{\"config\":{\"enabled\":true,\"mtu\":9000,\"name\":\"Vlan10\",\"type\":\"iana-if-type:l2vlan\"},\"name\":\"Vlan10\",\"openconfig-vlan:routed-vlan\":{\"openconfig-if-ip:ipv6\":{\"config\":{\"enabled\":false},\"state\":{\"enabled\":false}}},\"state\":{\"cpu\":false,\"logical\":true,\"management\":false,\"name\":\"Vlan10\",\"type\":\"iana-if-type:l2vlan\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"config\":{\"enabled\":false},\"state\":{\"enabled\":false}},\"state\":{\"index\":0}}]}}]}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -135,7 +135,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN modification ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan10\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9100, \"name\": \"Vlan10\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -147,7 +147,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN modification ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": false, \"mtu\": 9000, \"name\": \"Vlan10\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": false, \"mtu\": 9000, \"name\": \"Vlan10\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -159,7 +159,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN modification ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": false, \"mtu\": 9100, \"name\": \"Vlan10\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": false, \"mtu\": 9100, \"name\": \"Vlan10\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -171,7 +171,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- Verify VLAN modification ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config" - expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan10\"}}" + expected_get_json = "{\"openconfig-interfaces:config\": {\"enabled\": true, \"mtu\": 9000, \"name\": \"Vlan10\", \"type\": \"iana-if-type:l2vlan\"}}" t.Run("Test GET VLAN interface creation config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -184,7 +184,7 @@ func Test_openconfig_vlan_interface(t *testing.T) { t.Log("\n\n--- GET VLAN (state level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/state" - expected_get_json = "{\"openconfig-interfaces:state\":{\"admin-status\":\"UP\",\"enabled\":true,\"mtu\":9000,\"name\":\"Vlan10\"}}" + expected_get_json = "{\"openconfig-interfaces:state\":{\"admin-status\":\"UP\",\"enabled\":true,\"mtu\":9000,\"name\":\"Vlan10\",\"type\":\"iana-if-type:l2vlan\", \"cpu\":false,\"logical\":true,\"management\":false}}" t.Run("Test GET VLAN interface state config ", processGetRequest(url, nil, expected_get_json, false)) time.Sleep(1 * time.Second) @@ -385,7 +385,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (Eth, access) ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config/access-vlan" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN member (Eth, access) ---") @@ -403,7 +403,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (Eth, one trunk) ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config/trunk-vlans[trunk-vlans=40]" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN member (Eth, one trunk) ---") @@ -414,7 +414,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (Eth, all trunk) ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config/trunk-vlans" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN member (Eth, all trunk) ---") @@ -431,7 +431,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (PC, one trunk) ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel12]/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan/config/trunk-vlans[trunk-vlans=10]" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN member (PC, one trunk) ---") @@ -442,7 +442,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (PC, all trunk) ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel12]/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan/config/trunk-vlans" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN member (PC, all trunk) ---") @@ -459,7 +459,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE VLAN member (PC, access) ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel12]/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan/config/access-vlan" - t.Run("Test delete VLAN member", processDeleteRequest(url, true)) + t.Run("Test delete VLAN member", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted resource at VLAN member (PC, access) ---") @@ -497,7 +497,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE all VLAN members (Eth, config) ---") url = "/openconfig-interfaces:interfaces/interface[name=Ethernet0]/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config" - t.Run("Test delete all VLAN members", processDeleteRequest(url, true)) + t.Run("Test delete all VLAN members", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN members (Eth, switched-vlan) ---") @@ -526,7 +526,7 @@ func Test_openconfig_vlan_member(t *testing.T) { t.Log("\n\n--- DELETE all VLAN members (PC, switched-vlan) ---") url = "/openconfig-interfaces:interfaces/interface[name=PortChannel12]/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan" - t.Run("Test delete all VLAN members", processDeleteRequest(url, true)) + t.Run("Test delete all VLAN members", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN members (PC, switched-vlan config) ---") @@ -778,7 +778,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv4 (prefix-length leaf) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/addresses/address[ip=8.8.8.8]/config/prefix-length" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IP (prefix-length leaf) ---") @@ -789,7 +789,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv4 (specify address) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/addresses/address[ip=4.4.4.4]" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IP (specify address) ---") @@ -800,7 +800,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv4 (all addresses level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan20]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/addresses" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IP (all addresses level) ---") @@ -823,7 +823,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv4 (IPv4 level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IPv4 (IPv4 level) ---") @@ -834,7 +834,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv6 (prefix-length leaf) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/addresses/address[ip=2606:4700:4700::1111]/config/prefix-length" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IPv6 (prefix-length leaf) ---") @@ -857,7 +857,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv6 (specify address) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/addresses/address[ip=2606:4700:4700::1111]" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IPv6 (specify address) ---") @@ -868,7 +868,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv6 (all addresses level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/addresses" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IPv6 (all addresses level) ---") @@ -879,7 +879,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface IPv6 (IPv6 level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface IPv6 (IPv6 level) ---") @@ -914,7 +914,7 @@ func Test_openconfig_vlan_interface_ip(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface all IP (routed-vlan level) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/openconfig-vlan:routed-vlan" - t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface IP config", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface all IP (routed-vlan level) ---") @@ -937,7 +937,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- DELETE VLAN interface attribute (description) ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]/config/description" - t.Run("Test delete VLAN interface attribute", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface attribute", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface attribute (mtu) ---") @@ -949,7 +949,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 10 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan10]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -961,7 +961,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 20 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan20]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -973,7 +973,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 30 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan30]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -985,7 +985,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 40 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan40]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -997,7 +997,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 50 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan50]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -1009,7 +1009,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 60 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan60]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -1021,7 +1021,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 70 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan70]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") @@ -1033,7 +1033,7 @@ func Test_openconfig_vlan_interface_delete(t *testing.T) { t.Log("\n\n--- Delete Vlan 80 ---") url = "/openconfig-interfaces:interfaces/interface[name=Vlan80]" - t.Run("Test delete VLAN interface", processDeleteRequest(url, true)) + t.Run("Test delete VLAN interface", processDeleteRequest(url, false)) time.Sleep(1 * time.Second) t.Log("\n\n--- Verify deleted VLAN interface ---") diff --git a/translib/transformer/xfmr_intf.go b/translib/transformer/xfmr_intf.go index 560946911..13bd2ce3d 100644 --- a/translib/transformer/xfmr_intf.go +++ b/translib/transformer/xfmr_intf.go @@ -27,6 +27,7 @@ import ( "sort" "strconv" "strings" + "time" "inet.af/netaddr" @@ -48,6 +49,16 @@ func init() { XlateFuncBind("DbToYang_intf_admin_status_xfmr", DbToYang_intf_admin_status_xfmr) XlateFuncBind("YangToDb_intf_enabled_xfmr", YangToDb_intf_enabled_xfmr) XlateFuncBind("DbToYang_intf_enabled_xfmr", DbToYang_intf_enabled_xfmr) + XlateFuncBind("YangToDb_intf_type_xfmr", YangToDb_intf_type_xfmr) + XlateFuncBind("DbToYang_intf_type_xfmr", DbToYang_intf_type_xfmr) + XlateFuncBind("DbToYang_intf_description_xfmr", DbToYang_intf_description_xfmr) + XlateFuncBind("DbToYang_intf_ifindex_xfmr", DbToYang_intf_ifindex_xfmr) + XlateFuncBind("DbToYang_intf_oper_status_xfmr", DbToYang_intf_oper_status_xfmr) + XlateFuncBind("DbToYang_intf_last_change_xfmr", DbToYang_intf_last_change_xfmr) + XlateFuncBind("DbToYang_intf_mgmt_xfmr", DbToYang_intf_mgmt_xfmr) + XlateFuncBind("DbToYang_intf_cpu_xfmr", DbToYang_intf_cpu_xfmr) + XlateFuncBind("DbToYang_intf_logical_xfmr", DbToYang_intf_logical_xfmr) + XlateFuncBind("DbToYang_intf_eth_aggr_id_xfmr", DbToYang_intf_eth_aggr_id_xfmr) XlateFuncBind("YangToDb_intf_eth_port_config_xfmr", YangToDb_intf_eth_port_config_xfmr) XlateFuncBind("DbToYang_intf_eth_port_config_xfmr", DbToYang_intf_eth_port_config_xfmr) @@ -84,7 +95,6 @@ func init() { XlateFuncBind("intf_post_xfmr", intf_post_xfmr) XlateFuncBind("intf_pre_xfmr", intf_pre_xfmr) - XlateFuncBind("DbToYang_intf_routed_vlan_name_xfmr", DbToYang_intf_routed_vlan_name_xfmr) XlateFuncBind("YangToDb_intf_routed_vlan_name_xfmr", YangToDb_intf_routed_vlan_name_xfmr) XlateFuncBind("YangToDb_routed_vlan_ip_addr_xfmr", YangToDb_routed_vlan_ip_addr_xfmr) @@ -92,9 +102,14 @@ func init() { } const ( - PORT_ADMIN_STATUS = "admin_status" - PORT_SPEED = "speed" - PORT_AUTONEG = "autoneg" + PORT_ADMIN_STATUS = "admin_status" + PORT_SPEED = "speed" + PORT_AUTONEG = "autoneg" + PORT_OPER_STATUS = "oper_status" + PORT_LAST_UP_TIME = "last_up_time" + PORT_LAST_DOWN_TIME = "last_down_time" + PORT_IFINDEX = "index" + PORT_DESCRIPTION = "description" PORTCHANNEL_INTERFACE_TN = "PORTCHANNEL_INTERFACE" PORTCHANNEL_MEMBER_TN = "PORTCHANNEL_MEMBER" @@ -112,6 +127,7 @@ const ( ETHERNET = "Eth" PORTCHANNEL = "PortChannel" VLAN = "Vlan" + LOOPBACK = "Loopback" ) type TblData struct { @@ -152,10 +168,15 @@ var IntfTypeTblMap = map[E_InterfaceType]IntfTblData{ cfgDb: TblData{portTN: "VLAN", memberTN: "VLAN_MEMBER", intfTN: "VLAN_INTERFACE", keySep: PIPE}, appDb: TblData{portTN: "VLAN_TABLE", memberTN: "VLAN_MEMBER_TABLE", intfTN: "INTF_TABLE", keySep: COLON}, }, + IntfTypeLoopback: IntfTblData{ + cfgDb: TblData{portTN: "LOOPBACK_INTERFACE", intfTN: "LOOPBACK_INTERFACE", keySep: PIPE}, + appDb: TblData{portTN: "INTF_TABLE", intfTN: "INTF_TABLE", keySep: COLON}, + stateDb: TblData{portTN: "INTERFACE_TABLE", intfTN: "INTERFACE_TABLE", keySep: PIPE}, + }, } var dbIdToTblMap = map[db.DBNum][]string{ - db.ConfigDB: {"PORT", "PORTCHANNEL", "VLAN"}, + db.ConfigDB: {"PORT", "PORTCHANNEL", "VLAN", "LOOPBACK_INTERFACE"}, db.ApplDB: {"PORT_TABLE", "LAG_TABLE"}, db.StateDB: {"PORT_TABLE", "LAG_TABLE"}, } @@ -183,6 +204,7 @@ const ( IntfTypeEthernet E_InterfaceType = 1 IntfTypePortChannel E_InterfaceType = 2 IntfTypeVlan E_InterfaceType = 3 + IntfTypeLoopback E_InterfaceType = 4 ) type E_InterfaceSubType int64 @@ -200,6 +222,8 @@ func getIntfTypeByName(name string) (E_InterfaceType, E_InterfaceSubType, error) return IntfTypePortChannel, IntfSubTypeUnset, err } else if strings.HasPrefix(name, VLAN) { return IntfTypeVlan, IntfSubTypeUnset, err + } else if strings.HasPrefix(name, LOOPBACK) { + return IntfTypeLoopback, IntfSubTypeUnset, err } else { err = errors.New("Interface name prefix not matched with supported types") return IntfTypeUnset, IntfSubTypeUnset, err @@ -239,6 +263,7 @@ func performIfNameKeyXfmrOp(inParams *XfmrParams, requestUriPath *string, ifName if *requestUriPath == "/openconfig-interfaces:interfaces/interface" { switch ifType { + case IntfTypeVlan: /* VLAN Interface Delete Handling */ /* Update the map for VLAN and VLAN MEMBER table */ @@ -247,12 +272,23 @@ func performIfNameKeyXfmrOp(inParams *XfmrParams, requestUriPath *string, ifName log.Warningf("Deleting VLAN: %s failed! Err:%v", *ifName, err) return tlerr.InvalidArgsError{Format: err.Error()} } + case IntfTypePortChannel: err := deleteLagIntfAndMembers(inParams, ifName) if err != nil { log.Errorf("Deleting LAG: %s failed! Err:%v", *ifName, err) return tlerr.InvalidArgsError{Format: err.Error()} } + + case IntfTypeLoopback: + err = deleteAllIPsForLoopbackInterface(inParams, ifName) + + if err != nil { + log.Errorf("Deleting Loopback Interface: %s failed! Err:%v", *ifName, err) + return errors.New("Loopback interface is not found " + *ifName) + } + return err + case IntfTypeEthernet: err = validateIntfExists(inParams.d, IntfTypeTblMap[IntfTypeEthernet].cfgDb.portTN, *ifName) if err != nil { @@ -279,13 +315,13 @@ func performIfNameKeyXfmrOp(inParams *XfmrParams, requestUriPath *string, ifName return tlerr.InvalidArgsError{Format: errStr} } if inParams.oper == REPLACE { - if strings.Contains(*requestUriPath, "/openconfig-interfaces:interfaces/interface") { + if *requestUriPath == "/openconfig-interfaces:interfaces/interface" || *requestUriPath == "/openconfig-interfaces:interfaces/interface/config" || *requestUriPath == "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet" || *requestUriPath == "/openconfig-interfaces:interfaces/interface/ethernet" || *requestUriPath == "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/config" || *requestUriPath == "/openconfig-interfaces:interfaces/interface/ethernet/config" { if strings.Contains(*requestUriPath, "openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan") { if log.V(3) { log.Infof("allow replace operation for switched-vlan") } } else { - // OC interfaces yang does not have attributes to set Physical interface critical attributes like speed, alias, lanes, index. + // OC interfaces yang does not have attributes to set Physical interface critical attributes like speed. // Replace/PUT request without the critical attributes would end up in deletion of the same in PORT table, which cannot be allowed. // Hence block the Replace/PUT request for Physical interfaces alone. err_str := "Replace/PUT request not allowed for Physical interfaces" @@ -294,6 +330,24 @@ func performIfNameKeyXfmrOp(inParams *XfmrParams, requestUriPath *string, ifName } } } + if ifType == IntfTypePortChannel { + if inParams.oper == UPDATE { + err = validateIntfExists(inParams.d, IntfTypeTblMap[IntfTypePortChannel].cfgDb.portTN, *ifName) + if err != nil { //No Matching PortChannel to UPDATE/REPLACE + errStr := "PortChannel: " + *ifName + " does not exist" + return tlerr.InvalidArgsError{Format: errStr} + } + } + } + if ifType == IntfTypeLoopback { + if inParams.oper == UPDATE { + err = validateIntfExists(inParams.d, IntfTypeTblMap[IntfTypeLoopback].cfgDb.portTN, *ifName) + if err != nil { + errStr := "Loopback interface: " + *ifName + " does not exist" + return tlerr.InvalidArgsError{Format: errStr} + } + } + } } return err } @@ -464,11 +518,14 @@ var intf_table_xfmr TableXfmrFunc = func(inParams XfmrParams) ([]string, error) } else if intfType != IntfTypeEthernet && strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet") { //Checking interface type at container level, if not Ethernet type return nil - return nil, nil + return nil, tlerr.InvalidArgs("Container not supported for given interface type") } else if intfType != IntfTypePortChannel && strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-aggregate:aggregation") { //Checking interface type at container level, if not PortChannel type return nil - return nil, nil + return nil, errors.New("Container not supported for given interface type") + } else if intfType == IntfTypePortChannel && + strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-aggregate:aggregation/config") { + tblList = append(tblList, intTbl.cfgDb.portTN) } else if intfType != IntfTypeVlan && strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-vlan:routed-vlan") { //Checking interface type at container level, if not Vlan type return nil @@ -627,6 +684,7 @@ var DbToYang_intf_tbl_key_xfmr KeyXfmrDbToYang = func(inParams XfmrParams) (map[ var DbToYang_intf_admin_status_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { var err error + var status ocbinds.E_OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus result := make(map[string]interface{}) data := (*inParams.dbDataMap)[inParams.curDb] @@ -650,7 +708,6 @@ var DbToYang_intf_admin_status_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams } prtInst := pTbl[inParams.key] adminStatus, ok := prtInst.Field[PORT_ADMIN_STATUS] - var status ocbinds.E_OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus if ok { if adminStatus == "up" { status = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UP @@ -673,12 +730,31 @@ var YangToDb_intf_enabled_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (ma } enabled, _ := inParams.param.(*bool) + if enabled == nil { + return res_map, nil + } + + log.Infof("[YangToDb_intf_enabled_xfmr] enabled value: %v", *enabled) var enStr string if *enabled { + log.Infof("[YangToDb_intf_enabled_xfmr] enabled value: %v", *enabled) + enStr = "up" } else { enStr = "down" } + + pathInfo := NewPathInfo(inParams.uri) + ifName := pathInfo.Var("name") + intfType, _, ierr := getIntfTypeByName(ifName) + if intfType == IntfTypeUnset || ierr != nil { + return res_map, errors.New("Invalid interface type") + } + + if IntfTypeLoopback == intfType && (enStr == "down" || inParams.oper == DELETE) { + return res_map, errors.New("Disabling Loopback port is not supported") + } + res_map[PORT_ADMIN_STATUS] = enStr return res_map, nil @@ -742,6 +818,18 @@ var YangToDb_intf_name_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[s pathInfo := NewPathInfo(inParams.uri) ifName := pathInfo.Var("name") + intfType, _, err := getIntfTypeByName(ifName) + if intfType == IntfTypeUnset || err != nil { + log.Info("DbToYang_intf_name_xfmr - Invalid interface type IntfTypeUnset") + return res_map, errors.New("Invalid interface type IntfTypeUnset") + } + + err = errors.New("Invalid interface config/name received") + configName, ok := inParams.param.(*string) + if !ok || ifName != *configName { + return nil, err + } + if strings.HasPrefix(ifName, VLAN) { vlanId := ifName[len("Vlan"):] res_map["vlanid"] = vlanId @@ -749,9 +837,11 @@ var YangToDb_intf_name_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[s res_map["NULL"] = "NULL" } else if strings.HasPrefix(ifName, ETHERNET) { res_map["NULL"] = "NULL" + } else if strings.HasPrefix(ifName, LOOPBACK) { + res_map["NULL"] = "NULL" } log.Info("YangToDb_intf_name_xfmr: res_map:", res_map) - return res_map, err + return res_map, nil } var DbToYang_intf_name_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { @@ -766,16 +856,17 @@ var DbToYang_intf_name_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[s var YangToDb_intf_mtu_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { res_map := make(map[string]string) - var ifName string - intfsObj := getIntfsRoot(inParams.ygRoot) - if intfsObj == nil || len(intfsObj.Interface) < 1 { - return res_map, nil - } else { - for infK := range intfsObj.Interface { - ifName = infK - } - } + + pathInfo := NewPathInfo(inParams.uri) + uriIfName := pathInfo.Var("name") + ifName := uriIfName + intfType, _, _ := getIntfTypeByName(ifName) + // MTU is not supported for loopback interface in SONiC yang model + if IntfTypeLoopback == intfType { + log.Errorf("MTU configuration for Loopback type Interface: %s is NOT supported ", ifName) + return res_map, errors.New("Configuration for MTU is not supported for Loopback interface ") + } if inParams.oper == DELETE { log.Infof("Updating the Interface: %s with default MTU", ifName) @@ -869,6 +960,12 @@ var YangToDb_intf_eth_port_config_xfmr SubTreeXfmrYangToDb = func(inParams XfmrP return memMap, nil } + // check for loopback interface + if (intfType == IntfTypeLoopback) && inParams.oper != DELETE { + log.Info("YangToDb_intf_eth_port_config_xfmr: Configuration for ethernet container not supported for interface.") + return nil, errors.New("Error: Unsupported Interface: " + ifName) + } + intfsObj := getIntfsRoot(inParams.ygRoot) intfObj := intfsObj.Interface[uriIfName] @@ -944,6 +1041,12 @@ var YangToDb_intf_eth_port_config_xfmr SubTreeXfmrYangToDb = func(inParams XfmrP return nil, err } + prevLagId, err := retrievePortChannelAssociatedWithIntf(&inParams, &ifName) + if prevLagId != nil && *prevLagId != *lagId && inParams.oper != REPLACE { + errStr := ifName + " Interface is already member of " + *prevLagId + return nil, tlerr.InvalidArgsError{Format: errStr} + } + case DELETE: lagId, err := retrievePortChannelAssociatedWithIntf(&inParams, &ifName) if lagId != nil { @@ -972,6 +1075,10 @@ var YangToDb_intf_eth_port_config_xfmr SubTreeXfmrYangToDb = func(inParams XfmrP value := db.Value{Field: res_map} intTbl := IntfTypeTblMap[intfType] + if intfType != IntfTypeEthernet { + return nil, errors.New("Speed config not supported for given Interface type") + } + portSpeed := intfObj.Ethernet.Config.PortSpeed val, ok := intfOCToSpeedMap[portSpeed] if ok { @@ -1042,6 +1149,12 @@ var DbToYang_intf_eth_port_config_xfmr SubTreeXfmrDbToYang = func(inParams XfmrP err = tlerr.InvalidArgsError{Format: errStr} return err } + + //Check for loopback interface type + if intfType == IntfTypeLoopback { + return nil + } + intTbl := IntfTypeTblMap[intfType] tblName := intTbl.cfgDb.portTN entry, dbErr := inParams.dbs[db.ConfigDB].GetEntry(&db.TableSpec{Name: tblName}, db.Key{Comp: []string{ifName}}) @@ -1050,8 +1163,9 @@ var DbToYang_intf_eth_port_config_xfmr SubTreeXfmrDbToYang = func(inParams XfmrP err = tlerr.InvalidArgsError{Format: errStr} return err } + targetUriPath := pathInfo.YangPath - if strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/config") { + if strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/config") || strings.HasPrefix(targetUriPath, "/openconfig-interfaces:interfaces/interface/ethernet/config") { get_cfg_obj := false var intfObj *ocbinds.OpenconfigInterfaces_Interfaces_Interface if intfsObj != nil && intfsObj.Interface != nil && len(intfsObj.Interface) > 0 { @@ -1167,7 +1281,7 @@ var DbToYangPath_intf_eth_port_config_path_xfmr PathXfmrDbToYangFunc = func(para intfRoot := "/openconfig-interfaces:interfaces/interface" - if params.tblName != "PORT" { + if !(params.tblName == "PORT") { log.Info("DbToYangPath_intf_eth_port_config_path_xfmr: from wrong table: ", params.tblName) return nil } @@ -1185,10 +1299,8 @@ var DbToYangPath_intf_eth_port_config_path_xfmr PathXfmrDbToYangFunc = func(para } var DbToYang_intf_eth_auto_neg_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { - var err error result := make(map[string]interface{}) - data := (*inParams.dbDataMap)[inParams.curDb] intfType, _, ierr := getIntfTypeByName(inParams.key) if intfType == IntfTypeUnset || ierr != nil { log.Info("DbToYang_intf_eth_auto_neg_xfmr - Invalid interface type IntfTypeUnset") @@ -1199,9 +1311,17 @@ var DbToYang_intf_eth_auto_neg_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams } intTbl := IntfTypeTblMap[intfType] - tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) - pTbl := data[tblName] - prtInst := pTbl[inParams.key] + // https://github.com/sonic-net/sonic-buildimage/issues/9595 + tblName, _ := getPortTableNameByDBId(intTbl, db.ConfigDB) + d := inParams.dbs[db.ConfigDB] + pTbl := db.TableSpec{Name: tblName} + + prtInst, tblErr := d.GetEntry(&pTbl, db.Key{Comp: []string{inParams.key}}) + if tblErr != nil { + errStr := "Interface not found : " + inParams.key + return result, tlerr.InvalidArgsError{Format: errStr} + } + autoNeg, ok := prtInst.Field[PORT_AUTONEG] if ok { if autoNeg == "on" || autoNeg == "true" { @@ -1212,7 +1332,7 @@ var DbToYang_intf_eth_auto_neg_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams } else { log.Info("auto-negotiate field not found in DB") } - return result, err + return result, nil } var DbToYang_intf_eth_port_speed_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { @@ -1229,7 +1349,17 @@ var DbToYang_intf_eth_port_speed_xfmr FieldXfmrDbtoYang = func(inParams XfmrPara intTbl := IntfTypeTblMap[intfType] tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) + if _, ok := data[tblName]; !ok { + log.Info("DbToYang_intf_eth_port_speed_xfmr table not found : ", tblName) + return result, errors.New("table not found : " + tblName) + } + pTbl := data[tblName] + if _, ok := pTbl[inParams.key]; !ok { + log.Info("DbToYang_intf_eth_port_speed_xfmr key not found : ", inParams.key) + return result, errors.New("Interface not found : " + inParams.key) + } + prtInst := pTbl[inParams.key] speed, ok := prtInst.Field[PORT_SPEED] portSpeed := ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_UNSET @@ -1671,6 +1801,11 @@ var DbToYang_intf_get_ether_counters_xfmr SubTreeXfmrDbToYang = func(inParams Xf log.Info("DbToYang_intf_get_ether_counters_xfmr - Invalid interface type IntfTypeUnset") return errors.New("Invalid interface type IntfTypeUnset") } + // Check for loopback interface type + if intfType == IntfTypeLoopback { + log.Info("DbToYang_intf_get_ether_counters_xfmr - Loopback interface not supported") + return nil + } if !strings.Contains(targetUriPath, "/openconfig-interfaces:interfaces/interface/ethernet/state/counters") && !strings.Contains(targetUriPath, "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/state/counters") { @@ -1764,6 +1899,21 @@ var intf_pre_xfmr PreXfmrFunc = func(inParams XfmrParams) error { errStr += requestUriPath return tlerr.InvalidArgsError{Format: errStr} } + case "/openconfig-interfaces:interfaces/interface/config": + fallthrough + case "/openconfig-interfaces:interfaces/interface/config/type": + fallthrough + case "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet": + fallthrough + case "/openconfig-interfaces:interfaces/interface/ethernet": + fallthrough + case "/openconfig-interfaces:interfaces/interface/openconfig-if-ethernet:ethernet/config": + fallthrough + case "/openconfig-interfaces:interfaces/interface/ethernet/config": + if inParams.oper == DELETE { + errStr += requestUriPath + return tlerr.InvalidArgsError{Format: errStr} + } } } return err @@ -2441,7 +2591,7 @@ var DbToYang_intf_ip_addr_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) e return nil } - intfTypeList := [2]E_InterfaceType{IntfTypeEthernet, IntfTypePortChannel} + intfTypeList := [4]E_InterfaceType{IntfTypeEthernet, IntfTypePortChannel, IntfTypeLoopback, IntfTypeVlan} // Get IP from all configDb table interfaces for i := 0; i < len(intfTypeList); i++ { @@ -2642,7 +2792,9 @@ var YangToDb_intf_ip_addr_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) ( } intf_key := intf_intf_tbl_key_gen(ifName, *addr.Config.Ip, int(*addr.Config.PrefixLength), "|") - m["family"] = "IPv4" + if intfType != IntfTypePortChannel { + m["family"] = "IPv4" + } value := db.Value{Field: m} if _, ok := subIntfmap[tblName]; !ok { @@ -2681,8 +2833,9 @@ var YangToDb_intf_ip_addr_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) ( intf_key := intf_intf_tbl_key_gen(ifName, *addr.Config.Ip, int(*addr.Config.PrefixLength), "|") - m["family"] = "IPv6" - + if intfType != IntfTypePortChannel { + m["family"] = "IPv6" + } value := db.Value{Field: m} if _, ok := subIntfmap[tblName]; !ok { subIntfmap[tblName] = make(map[string]db.Value) @@ -2788,6 +2941,7 @@ func intf_intf_tbl_key_gen(intfName string, ip string, prefixLen int, keySep str ipStr, _ := getNormalizedIpStr(ip) return intfName + keySep + ipStr + "/" + strconv.Itoa(prefixLen) } + func parseCIDR(ipPref string) (netaddr.IP, netaddr.IPPrefix, error) { prefIdx := strings.LastIndexByte(ipPref, '/') if prefIdx <= 0 { @@ -2803,6 +2957,7 @@ func parseCIDR(ipPref string) (netaddr.IP, netaddr.IPPrefix, error) { ipNetA, _ := ipA.Prefix(uint8(prefLen)) return ipA, ipNetA, nil } + func getIntfIpByName(dbCl *db.DB, tblName string, ifName string, ipv4 bool, ipv6 bool, ip string) (map[string]db.Value, error) { var err error intfIpMap := make(map[string]db.Value) @@ -3673,7 +3828,7 @@ var DbToYangPath_intf_ip_path_xfmr PathXfmrDbToYangFunc = func(params XfmrDbToYg params.ygPathKeys[ifRoot+"/name"] = ifParts[0] if params.tblName == "INTERFACE" || params.tblName == "VLAN_INTERFACE" || - params.tblName == "INTF_TABLE" || params.tblName == "PORTCHANNEL_INTERFACE" { + params.tblName == "INTF_TABLE" || params.tblName == "PORTCHANNEL_INTERFACE" || params.tblName == "MGMT_INTERFACE" || params.tblName == "MGMT_INTERFACE" { addrPath := "/openconfig-if-ip:ipv4/addresses/address/ip" @@ -3749,6 +3904,11 @@ var YangToDb_ipv6_enabled_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (ma if ierr != nil || intfType == IntfTypeUnset { return res_map, errors.New("YangToDb_ipv6_enabled_xfmr, Error: Unsupported Interface: " + ifUIName) } + // ipv6 Enabled leaf is not available in SONiC yang model for loopback interface + if intfType == IntfTypeLoopback { + log.Info("YangToDb_ipv6_enabled_xfmr: Configuration for ipv6 enabled not supported for given interface.") + return res_map, nil + } if ifUIName == "" { errStr := "Interface KEY not present" @@ -3858,6 +4018,11 @@ var DbToYang_ipv6_enabled_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (ma log.Info("Interface Name = ", *ifUIName) intfType, _, _ := getIntfTypeByName(inParams.key) + // ipv6 Enabled leaf is not available in SONiC yang model for loopback interface + if intfType == IntfTypeLoopback { + log.Info("DbToYang_ipv6_enabled_xfmr not supported for given interface ") + return res_map, nil + } intTbl := IntfTypeTblMap[intfType] tblName, _ := getIntfTableNameByDBId(intTbl, inParams.curDb) @@ -3922,6 +4087,42 @@ func retrievePortChannelAssociatedWithIntf(inParams *XfmrParams, ifName *string) return nil, err } +func deleteAllIPsForLoopbackInterface(inParams *XfmrParams, ifName *string) error { + + subOpMap := make(map[db.DBNum]map[string]map[string]db.Value) + subIntfmap_del := make(map[string]map[string]db.Value) + intfType, _, ierr := getIntfTypeByName(*ifName) + if ierr != nil || intfType != IntfTypeLoopback { + return tlerr.InvalidArgsError{Format: "Invalid Loopback: " + *ifName} + } + + intTbl := IntfTypeTblMap[intfType] + tblName, _ := getIntfTableNameByDBId(intTbl, inParams.curDb) + + entry, dbErr := inParams.d.GetEntry(&db.TableSpec{Name: intTbl.cfgDb.intfTN}, db.Key{Comp: []string{*ifName}}) + if dbErr != nil || !entry.IsPopulated() { + // Not returning error from here since mgmt infra will return "Resource not found" error in case of non-existence entries + return nil + } + + subIntfmap_del[tblName] = make(map[string]db.Value) + subIntfmap_del[tblName][*ifName] = db.Value{Field: map[string]string{}} + + intfTable := &db.TableSpec{Name: tblName} + intfKeys, err := inParams.d.GetKeysPattern(intfTable, db.Key{Comp: []string{*ifName, "*"}}) + + if err == nil && len(intfKeys) > 0 { + for _, intfKey := range intfKeys { + ipPrefix := intfKey.Comp[1] + key := *ifName + "|" + ipPrefix + subIntfmap_del[tblName][key] = db.Value{Field: map[string]string{}} + } + } + subOpMap[db.ConfigDB] = subIntfmap_del + inParams.subOpDataMap[DELETE] = &subOpMap + return nil +} + var DbToYang_routed_vlan_ip_addr_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { var err error intfsObj := getIntfsRoot(inParams.ygRoot) @@ -3967,3 +4168,288 @@ var DbToYang_routed_vlan_ip_addr_xfmr SubTreeXfmrDbToYang = func(inParams XfmrPa return err } + +var DbToYang_intf_type_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + + intfType, _, err := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || err != nil { + log.Info("DbToYang_intf_type_xfmr - Invalid interface type IntfTypeUnset") + return result, errors.New("Invalid interface type IntfTypeUnset") + } + + if intfType == IntfTypeEthernet { + result["type"] = "ethernetCsmacd" + } else if intfType == IntfTypePortChannel { + result["type"] = "ieee8023adLag" + } else if intfType == IntfTypeLoopback { + result["type"] = "softwareLoopback" + } else if intfType == IntfTypeVlan { + result["type"] = "l2vlan" + } + + return result, nil +} + +var YangToDb_intf_type_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + + pathInfo := NewPathInfo(inParams.uri) + ifName := pathInfo.Var("name") + intfType, _, err := getIntfTypeByName(ifName) + if intfType == IntfTypeUnset || err != nil { + return res_map, errors.New("Invalid interface type") + } + + interfaceType, ok := inParams.param.(ocbinds.E_IETFInterfaces_InterfaceType) + if !ok { + return nil, errors.New("Unsupported interface type") + } + + if (intfType == IntfTypeEthernet && interfaceType != ocbinds.IETFInterfaces_InterfaceType_ethernetCsmacd) || + (intfType == IntfTypePortChannel && interfaceType != ocbinds.IETFInterfaces_InterfaceType_ieee8023adLag) || + (intfType == IntfTypeVlan && interfaceType != ocbinds.IETFInterfaces_InterfaceType_l2vlan) || + (intfType == IntfTypeLoopback && interfaceType != ocbinds.IETFInterfaces_InterfaceType_softwareLoopback) { + return res_map, errors.New("Unsupported interface type") + } + + return res_map, nil +} + +var DbToYang_intf_description_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + result := make(map[string]interface{}) + + data := (*inParams.dbDataMap)[inParams.curDb] + + intfType, _, ierr := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || ierr != nil { + return result, errors.New("Invalid interface type") + } + + intTbl := IntfTypeTblMap[intfType] + + tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) + if _, ok := data[tblName]; !ok { + log.Info("DbToYang_intf_description_xfmr table not found : ", tblName) + return result, errors.New("table not found : " + tblName) + } + + pTbl := data[tblName] + if _, ok := pTbl[inParams.key]; !ok { + log.Info("DbToYang_intf_description_xfmr Interface not found : ", inParams.key) + return result, errors.New("Interface not found : " + inParams.key) + } + prtInst := pTbl[inParams.key] + desc, ok := prtInst.Field[PORT_DESCRIPTION] + if ok && desc != "" { + result["description"] = desc + } else { + log.Info("Description field not found in DB") + } + + return result, nil +} + +var DbToYang_intf_oper_status_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var status ocbinds.E_OpenconfigInterfaces_Interfaces_Interface_State_OperStatus + result := make(map[string]interface{}) + + data := (*inParams.dbDataMap)[inParams.curDb] + + intfType, _, ierr := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || ierr != nil { + return result, errors.New("Invalid interface type") + } + + intTbl := IntfTypeTblMap[intfType] + + tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) + if _, ok := data[tblName]; !ok { + log.Info("DbToYang_intf_oper_status_xfmr table not found : ", tblName) + return result, errors.New("table not found : " + tblName) + } + + pTbl := data[tblName] + if _, ok := pTbl[inParams.key]; !ok { + log.Info("DbToYang_intf_oper_status_xfmr Interface not found : ", inParams.key) + return result, errors.New("Interface not found : " + inParams.key) + } + prtInst := pTbl[inParams.key] + + var operStatus string + var ok bool + operStatus, ok = prtInst.Field[PORT_OPER_STATUS] + + if ok { + if operStatus == "up" { + status = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UP + } else { + status = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_DOWN + } + result["oper-status"] = ocbinds.E_OpenconfigInterfaces_Interfaces_Interface_State_OperStatus.ΛMap(status)["E_OpenconfigInterfaces_Interfaces_Interface_State_OperStatus"][int64(status)].Name + } else { + log.Info("Oper status field not found in DB") + } + + return result, nil +} + +var DbToYang_intf_ifindex_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + result := make(map[string]interface{}) + + data := (*inParams.dbDataMap)[inParams.curDb] + + intfType, _, ierr := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || ierr != nil { + return result, errors.New("Invalid interface type") + } + + intTbl := IntfTypeTblMap[intfType] + + tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) + if _, ok := data[tblName]; !ok { + log.Info("DbToYang_intf_ifindex_xfmr table not found : ", tblName) + return result, errors.New("table not found : " + tblName) + } + + pTbl := data[tblName] + if _, ok := pTbl[inParams.key]; !ok { + log.Info("DbToYang_intf_ifindex_xfmr Interface not found : ", inParams.key) + return result, errors.New("Interface not found : " + inParams.key) + } + prtInst := pTbl[inParams.key] + indexStr, ok := prtInst.Field[PORT_IFINDEX] + if ok { + index, err := strconv.ParseUint(indexStr, 10, 32) + if err != nil { + return result, err + } + result["ifindex"] = index + } else { + log.Info("Port index field not found in DB") + } + + return result, nil +} + +var DbToYang_intf_last_change_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + + data := (*inParams.dbDataMap)[inParams.curDb] + + intfType, _, ierr := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || ierr != nil { + return result, errors.New("Invalid interface type") + } + + intTbl := IntfTypeTblMap[intfType] + + tblName, _ := getPortTableNameByDBId(intTbl, inParams.curDb) + if _, ok := data[tblName]; !ok { + log.Info("DbToYang_intf_last_change_xfmr table not found : ", tblName) + return result, errors.New("table not found : " + tblName) + } + + pTbl := data[tblName] + if _, ok := pTbl[inParams.key]; !ok { + log.Info("DbToYang_intf_last_change_xfmr Interface not found : ", inParams.key) + return result, errors.New("Interface not found : " + inParams.key) + } + prtInst := pTbl[inParams.key] + + lastUpTimeStr, okup := prtInst.Field[PORT_LAST_UP_TIME] + lastDownTimeStr, okdown := prtInst.Field[PORT_LAST_DOWN_TIME] + if okup || okdown { + + // Define the layout that matches the format of the date-time strings + layout := "Mon Jan 02 15:04:05 2006" + + var lastUpTime, lastDownTime time.Time + // Parse the date-time strings into time.Time objects + if okup { + lastUpTime, err = time.Parse(layout, lastUpTimeStr) + // Check for parsing errors + if err != nil { + return result, errors.New("Error parsing date-time strings") + } + } + + if okdown { + lastDownTime, err = time.Parse(layout, lastDownTimeStr) + if err != nil { + return result, errors.New("Error parsing date-time strings") + } + } + + // Compare the two time.Time objects + var recentTime time.Time + if okup && okdown { + if lastUpTime.After(lastDownTime) { + recentTime = lastUpTime + } else { + recentTime = lastDownTime + } + } else if okup { + recentTime = lastUpTime + } else { + recentTime = lastDownTime + } + + // Calculate TimeTicks64 since Unix epoch (January 1, 1970) + epoch := time.Unix(0, 0) + durationSinceEpoch := recentTime.Sub(epoch) + timeTicks64 := durationSinceEpoch.Nanoseconds() / 10_000_000 // Convert nanoseconds to hundredths of a second + + result["last-change"] = strconv.FormatInt(timeTicks64, 10) + } else { + log.Info("last-change field not found in DB") + } + return result, nil +} + +var DbToYang_intf_logical_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + + intfType, _, err := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || err != nil { + return result, errors.New("Invalid interface type") + } + + if intfType == IntfTypeEthernet { + result["logical"] = false + } else { + result["logical"] = true + } + + return result, nil +} + +var DbToYang_intf_mgmt_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + + intfType, _, err := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || err != nil { + return result, errors.New("Invalid interface type") + } + + result["management"] = false + return result, nil +} + +var DbToYang_intf_cpu_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + + intfType, _, err := getIntfTypeByName(inParams.key) + if intfType == IntfTypeUnset || err != nil { + return result, errors.New("Invalid interface type") + } + + // cpu port not supported + result["cpu"] = false + return result, nil +} diff --git a/translib/transformer/xlate_from_db.go b/translib/transformer/xlate_from_db.go index 7cb36caf3..293932567 100644 --- a/translib/transformer/xlate_from_db.go +++ b/translib/transformer/xlate_from_db.go @@ -2079,7 +2079,7 @@ func dbDataToYangJsonCreate(inParamsForGet xlateFromDbParams) (string, bool, err "ygNode: %v, ygot parent obj: %v; inParamsForGet.relUri: %v; error: %v", inParamsForGet.uri, parentUriPath, inParamsForGet.ygSchema.Name, reflect.TypeOf(*inParamsForGet.ygParentObj), inParamsForGet.relUri, err) return "", true, err - } else { + } else if ygotCtx.trgtYgObj != nil { inParamsForGet.ygParentObj = ygotCtx.trgtYgObj inParamsForGet.ygSchema = ygotCtx.trgtYgSchema inParamsForGet.relUri = uriPathList[len(uriPathList)-1]