L2VPN Package Prototype

template

<config-template xmlns="http://tail-f.com/ns/config/1.0"
                 xmlns:ncs="http://tail-f.com/ns/ncs">
  <devices xmlns="http://tail-f.com/ns/ncs">
    <device>
      <name>{$DEVICE}</name> <config>
        <interface xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <GigabitEthernet-subinterface>
            <GigabitEthernet>
              <id>{$MAIN_INTERFACE_NAME}</id> 
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <default ncs:when="{$L2_SERV_ENCAP = 'default'}"/>
                <dot1q ncs:when="{$L2_SERV_ENCAP = 'dot1q'}">
                  <vlan-id ncs:when="{$L2_SERV_VLAN_ID != 0}">{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
                <untagged ncs:when="{$L2_SERV_ENCAP = 'untagged'}"/>
              </encapsulation>
              <rewrite ncs:when="{$L2_SERV_ENCAP = 'dot1q' and $L2_SERV_INGRESS_REWRITE_TYPE != 'none'}">
                <ingress>
                  <tag>
                    <pop ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'pop1'}">1</pop>
                    <translate ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">1-to-1</translate>
                    <dot1q ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1' and $L2_SERV_INGRESS_REWRITE_VLAN != 0}">{$L2_SERV_INGRESS_REWRITE_VLAN}</dot1q>
                    <mode>symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </GigabitEthernet>
          </GigabitEthernet-subinterface>
        </interface>

        <l2vpn xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <pw-class>
            <name>{$PW_CLASS_NAME}</name>
            <encapsulation>
              <mpls>
                <control-word ncs:when="{$CONTROL_WORD_ENABLED = 'true'}"/> </mpls>
            </encapsulation>
          </pw-class>
          <xconnect>
            <group>
              <name>{$XCONNECT_GROUP_NAME}</name>
              <p2p>
                <name>{$P2P_NAME}</name>
                <interface>
                  <name>{$FULL_INTERFACE_NAME}</name> </interface>
                <neighbor>
                  <address>{$NEIGHBOR_IP}</address>
                  <pw-id>{$PW_ID}</pw-id>
                  <ip-version>ipv4</ip-version>
                  <pw-class>{$PW_CLASS_REF}</pw-class>
                </neighbor>
              </p2p>
            </group>
          </xconnect>
        </l2vpn>
      </config>
    </device>
  </devices>
</config-template>

python

import ncs
from ncs.application import Service

class ServiceCallbacks(Service):

    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create(service='{service._path}')")

        device_name = service.device
        full_interface_name = service.local_l2transport_interface.interface_name
        mtu = service.local_l2transport_interface.mtu
        encapsulation_type = service.local_l2transport_interface.encapsulation_type
        
        main_interface_name = full_interface_name 
        if full_interface_name.startswith("GigabitEthernet"):
            main_interface_name = full_interface_name[len("GigabitEthernet"):]
        l2_serv_vlan_id = 0 
        if encapsulation_type == 'dot1q':
            l2_serv_vlan_id = service.local_l2transport_interface.vlan_id

        l2_serv_ingress_rewrite_type = 'none'
        l2_serv_ingress_rewrite_vlan = 0 

        if encapsulation_type == 'dot1q' and hasattr(service.local_l2transport_interface, 'rewrite_policy'):
            rewrite_policy = service.local_l2transport_interface.rewrite_policy
            l2_serv_ingress_rewrite_type = rewrite_policy.rewrite_type
            if l2_serv_ingress_rewrite_type == 'translate1to1':
                if hasattr(rewrite_policy, 'rewrite_vlan_id'):
                    l2_serv_ingress_rewrite_vlan = rewrite_policy.rewrite_vlan_id

        pw_class_name = service.pw_class.pw_class_name
        control_word_enabled_str = 'true' if service.pw_class.control_word else 'false'

        xconnect_group_name = service.xconnect.group_name
        p2p_name = service.xconnect.p2p_name
        neighbor_ip = service.xconnect.neighbor_ip
        pw_id = service.xconnect.pw_id
        pw_class_ref = service.xconnect.pw_class_ref


        tvars = ncs.template.Variables()
        tvars.add('DEVICE', device_name)
        tvars.add('FULL_INTERFACE_NAME', full_interface_name) 
        tvars.add('MAIN_INTERFACE_NAME', main_interface_name) 

        tvars.add('MTU', mtu)
        tvars.add('L2_SERV_ENCAP', encapsulation_type)
        tvars.add('L2_SERV_VLAN_ID', l2_serv_vlan_id) 
        tvars.add('L2_SERV_INGRESS_REWRITE_TYPE', l2_serv_ingress_rewrite_type)
        tvars.add('L2_SERV_INGRESS_REWRITE_VLAN', l2_serv_ingress_rewrite_vlan)
        tvars.add('PW_CLASS_NAME', pw_class_name)
        tvars.add('CONTROL_WORD_ENABLED', control_word_enabled_str)

        tvars.add('XCONNECT_GROUP_NAME', xconnect_group_name)
        tvars.add('P2P_NAME', p2p_name)
        tvars.add('NEIGHBOR_IP', neighbor_ip)
        tvars.add('PW_ID', pw_id)
        tvars.add('PW_CLASS_REF', pw_class_ref)

        template = ncs.template.Template(service)
        template.apply('l2vpn-template', tvars)

    @Service.pre_modification
    def cb_pre_modification(self, tctx, op, kp, root, proplist):
        self.log.info(f"Service pre_modification(service='{kp}', op='{op}')")
        pass

    @Service.post_modification
    def cb_post_modification(self, tctx, op, kp, root, proplist):
        self.log.info(f"Service post_modification(service='{kp}', op='{op}')")
        pass

class L2vpn(ncs.application.Application):
    def setup(self):
        self.log.info('L2vpn RUNNING')
        self.register_service('l2vpn-servicepoint', ServiceCallbacks)

    def teardown(self):
        self.log.info('L2vpn FINISHED')

yang

module l2vpn {
  namespace "http://example.com/l2vpn";
  prefix l2vpn;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-ncs {
    prefix ncs;
  }

  description "L2VPN service model for Cisco IOS XR Router (xrv9k-fullk9-6.4.1).
               This model configures point-to-point L2VPN services including
               pseudowire class and xconnect group settings, along with
               associated l2transport interface parameters.";

  revision 2025-07-07 {
    description "Added 'default' encapsulation type based on device config XML.";
  }

  revision 2025-07-05 {
    description "Adjusted YANG model based on NSO 'show full-configuration | display xml' output for IOS XR NED.
                 Modified interface, pw-class, and xconnect structures for accurate mapping.";
  }

  revision 2025-07-04 {
    description "Updated revision to include l2transport interface parameters (interface-name, mtu)
                 and align with corrected Python and XML template logic, and fix compilation errors.";
  }

  list l2vpn {
    key name;
    description "List of L2VPN service instances.";

    uses ncs:service-data;

    leaf name {
      type string;
      description "Unique name for this L2VPN service instance.";
    }

    leaf device {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      mandatory true;
      description "The target device on which to configure the L2VPN.";
    }

    container local-l2transport-interface {
      description "Configuration for the local L2 transport interface.";
      leaf interface-name {
        type string; // e.g., GigabitEthernet0/0/0/0.1
        mandatory true;
        description "The full name of the local interface including subinterface ID (e.g., GigabitEthernet0/0/0/0.1).";
      }
      leaf mtu {
        type uint16;
        default 1522;
        description "MTU for the L2 transport interface.";
      }

      leaf encapsulation-type {
        type enumeration {
          enum "dot1q";
          enum "untagged";
          enum "default"; 
        }
        mandatory true;
        description "Encapsulation type for the L2 transport interface (dot1q, untagged, or default).";
      }
      leaf vlan-id {
          when "../encapsulation-type = 'dot1q'";
          type uint16;
          mandatory true;
          description "VLAN ID for dot1q encapsulation.";
      }

      container rewrite-policy {
        when "../encapsulation-type = 'dot1q'";
        description "Ingress rewrite policy settings.";

        leaf rewrite-type {
          type enumeration {
            enum "none" {
              description "No rewrite.";
            }
            enum "pop1" {
              description "Pop 1 VLAN tag.";
            }
            enum "translate1to1" {
              description "Translate 1-to-1 VLAN tag.";
            }
          }
          default "none";
          description "Type of ingress rewrite operation.";
        }

        leaf rewrite-vlan-id {
          when "../rewrite-type = 'translate1to1'";
          type uint16;
          mandatory true;
          description "VLAN ID for 1-to-1 translate rewrite.";
        }
      }
    }

    container pw-class {
      description "Pseudowire class configuration for the L2VPN.";
      leaf pw-class-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the pseudowire class.";
      }
      leaf control-word {
        type boolean;
        default "false";
        description "Enable or disable control-word for the pseudowire.";
      }
    }

    container xconnect {
      description "Xconnect group and point-to-point connection configuration.";
      leaf group-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the xconnect group (e.g., Provisioned).";
      }
      leaf p2p-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the point-to-point connection (e.g., epl1).";
      }
      leaf neighbor-ip {
        type inet:ipv4-address;
        mandatory true;
        description "Neighbor IPv4 address for the pseudowire (e.g., 10.10.10.10).";
      }
      leaf pw-id {
        type uint32 {
          range "1..65535";
        }
        mandatory true;
        description "Pseudowire ID (e.g., 13).";
      }
      leaf pw-class-ref {
        type leafref {
          path "../../pw-class/pw-class-name";
        }
        mandatory true;
        description "Reference to the pseudowire class to be used for this xconnect.";
      }
    }

    ncs:servicepoint "l2vpn-servicepoint";
  }
}

package

<ncs-package xmlns="http://tail-f.com/ns/ncs-packages">
  <name>l2vpn</name>
  <package-version>1.0</package-version>
  <description>Generated Python package</description>
  <ncs-min-version>6.5</ncs-min-version>

  <component>
    <name>l2vpn</name>
    <application>
      <python-class-name>l2vpn.l2vpn.L2vpn</python-class-name>
    </application>
  </component>
</ncs-package>


설정과정

root@ncs(config)# devices sync-from 
sync-result {
    device R1
    result true
}
sync-result {
    device R2
    result true
}
sync-result {
    device R3
    result true
}
root@ncs(config)# l2vpn my-l2vpn-r1-pe
Value for 'device' [R1,R2,R3]: R1
Value for 'local-l2transport-interface interface-name' (<string>): GigabitEthernet0/0/0/1.1
Value for 'local-l2transport-interface encapsulation-type' [default,dot1q,untagged]: default
Value for 'pw-class pw-class-name' (<string>): PWClass_13_30-30-30-30
Value for 'xconnect group-name' (<string>): L2VPN_Group
Value for 'xconnect p2p-name' (<string>): P2P_R1_R3
Value for 'xconnect neighbor-ip' (<IPv4 address>): 30.30.30.30
Value for 'xconnect pw-id' (<unsignedInt, 1 .. 65535>): 13
Value for 'xconnect pw-class-ref' [PWClass_13_30-30-30-30]: PWClass_13_30-30-30-30 
root@ncs(config-l2vpn-my-l2vpn-r1-pe)# commit dry-run 
cli {
    local-node {
        data  devices {
                  device R1 {
                      config {
                          l2vpn {
             +                pw-class PWClass_13_30-30-30-30 {
             +                    encapsulation {
             +                        mpls {
             +                        }
             +                    }
             +                }
                              xconnect {
             +                    group L2VPN_Group {
             +                        p2p P2P_R1_R3 {
             +                            interface GigabitEthernet0/0/0/1.1;
             +                            neighbor 30.30.30.30 13 {
             +                                ip-version ipv4;
             +                                pw-class PWClass_13_30-30-30-30;
             +                            }
             +                        }
             +                    }
                              }
                          }
                      }
                  }
              }
             +l2vpn my-l2vpn-r1-pe {
             +    device R1;
             +    local-l2transport-interface {
             +        interface-name GigabitEthernet0/0/0/1.1;
             +        encapsulation-type default;
             +    }
             +    pw-class {
             +        pw-class-name PWClass_13_30-30-30-30;
             +    }
             +    xconnect {
             +        group-name L2VPN_Group;
             +        p2p-name P2P_R1_R3;
             +        neighbor-ip 30.30.30.30;
             +        pw-id 13;
             +        pw-class-ref PWClass_13_30-30-30-30;
             +    }
             +}
    }
}
root@ncs(config-l2vpn-my-l2vpn-r1-pe)# commit