l2vpn version3

l2vpn.yang

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

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

  description "A service for managing multiple L2VPN point-to-point connections.";

  revision 2025-08-06 {
    description "Refactored to manage a list of P2P connections using xconnect.";
  }

  list l2vpn {
    key name;
    uses ncs:service-data;
    ncs:servicepoint "l2vpn-servicepoint";

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

    list p2p-connection {
      key "connection-name";
      description "A list of individual P2P connections.";

      leaf connection-name { type string; }

      leaf pw-id { type uint32; mandatory true; }
      leaf pw-class-name { type string; mandatory true; }
      leaf control-word { type boolean; default "false"; }
      leaf group-name { type string; mandatory true; }
      leaf vlan-id { type uint16; mandatory true; }
      container endpoint-A {
        uses L2VPN-ENDPOINT;
      }
      container endpoint-B {
        uses L2VPN-ENDPOINT;
      }
    }
  }

  grouping L2VPN-ENDPOINT {
    leaf device {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      mandatory true;
    }
    leaf pe-neighbor-ip {
      type inet:ipv4-address;
      mandatory true;
      description "The IP address of this PE router for peering (e.g., Loopback IP).";
    }
    leaf interface-type {
      type enumeration {
        enum "GigabitEthernet";
        enum "TenGigE";
        enum "HundredGigE";
      }
      mandatory true;
    }
    leaf interface-main-id { type string; mandatory true; }
    leaf subinterface-id { type uint32; mandatory true; }
    leaf p2p-name { type string; mandatory true; }
    leaf mtu { type uint16; default 1522; }

    container rewrite-policy {
      leaf rewrite-type {
        type enumeration {
          enum "none";
          enum "pop1";
          enum "translate1to1";
        }
        default "none";
      }
      leaf rewrite-vlan-id {
        when "../rewrite-type = 'translate1to1'";
        type uint16 { range "1..4094"; }
        mandatory true;
      }
    }
  }
}

l2vpn.py

import ncs
from ncs.application import Service, Application

class ServiceCallbacks(Service):
    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        template = ncs.template.Template(service)

        for connection in service.p2p_connection:
            endpoint_a = connection.endpoint_A
            endpoint_b = connection.endpoint_B
            
            tvars_a = self.get_tvars(connection, endpoint_a, endpoint_b.pe_neighbor_ip)
            template.apply('l2vpn-template', tvars_a)

            tvars_b = self.get_tvars(connection, endpoint_b, endpoint_a.pe_neighbor_ip)
            template.apply('l2vpn-template', tvars_b)

    def get_tvars(self, conn, local_ep, remote_ip):
        tvars = ncs.template.Variables()
        
        tvars.add('PW_ID', conn.pw_id)
        tvars.add('PW_CLASS_NAME', conn.pw_class_name)
        tvars.add('CONTROL_WORD_ENABLED', 'true' if conn.control_word else 'false')
        tvars.add('XCONNECT_GROUP_NAME', conn.group_name)
        tvars.add('PW_CLASS_REF', conn.pw_class_name) 
        tvars.add('L2_SERV_VLAN_ID', conn.vlan_id)

        tvars.add('DEVICE', local_ep.device)
        full_if_name = f"{local_ep.interface_type}{local_ep.interface_main_id}.{local_ep.subinterface_id}"
        tvars.add('INTERFACE_TYPE', local_ep.interface_type)
        tvars.add('INTERFACE_ID', f"{local_ep.interface_main_id}.{local_ep.subinterface_id}")
        tvars.add('FULL_INTERFACE_NAME', full_if_name)
        tvars.add('MTU', local_ep.mtu)
        tvars.add('L2_SERV_ENCAP', 'dot1q')
        tvars.add('P2P_NAME', local_ep.p2p_name)

        tvars.add('NEIGHBOR_IP', remote_ip)

        l2_serv_ingress_rewrite_type = 'none'
        l2_serv_ingress_rewrite_vlan = 0
        if hasattr(local_ep, 'rewrite_policy'):
            rewrite_policy = local_ep.rewrite_policy
            l2_serv_ingress_rewrite_type = rewrite_policy.rewrite_type
            if l2_serv_ingress_rewrite_type == 'translate1to1' and hasattr(rewrite_policy, 'rewrite_vlan_id'):
                l2_serv_ingress_rewrite_vlan = rewrite_policy.rewrite_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)

        return tvars

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')

l2vpn-template.xml

<config 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 ncs:when="{$INTERFACE_TYPE = 'GigabitEthernet'}">
            <GigabitEthernet>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <dot1q>
                  <vlan-id>{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
              </encapsulation>
              <rewrite ncs:when="{$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 ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </GigabitEthernet>
          </GigabitEthernet-subinterface>

          <TenGigE-subinterface ncs:when="{$INTERFACE_TYPE = 'TenGigE'}">
            <TenGigE>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <dot1q>
                  <vlan-id>{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
              </encapsulation>
              <rewrite ncs:when="{$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 ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </TenGigE>
          </TenGigE-subinterface>
          
          <HundredGigE-subinterface ncs:when="{$INTERFACE_TYPE = 'HundredGigE'}">
            <HundredGigE>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <dot1q>
                  <vlan-id>{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
              </encapsulation>
              <rewrite ncs:when="{$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 ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </HundredGigE>
          </HundredGigE-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>

사용 방법

roo@ncs(config)# l2vpn exam
root@ncs(config-l2vpn-exam)# p2p-connection VLAN10
Value for 'pw-id' (<unsignedInt>): 1010
Value for 'pw-class-name' (<string>): L2VPN-CLASS
Value for 'group-name' (<string>): L2VPN-GROUP
Value for 'vlan-id' (<unsignedShort>): 10
Value for 'endpoint-A device' [CE01,CE1,CE02,CE2,...]: R1
Value for 'endpoint-A pe-neighbor-ip' (<IPv4 address>): 10.10.10.10
Value for 'endpoint-A interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'endpoint-A interface-main-id' (<string>): 0/0/0/2
Value for 'endpoint-A subinterface-id' (<unsignedInt>): 10
Value for 'endpoint-A p2p-name' (<string>): R1_to_R3_VLAN10
Value for 'endpoint-B device' [CE01,CE1,CE02,CE2,...]: R3
Value for 'endpoint-B pe-neighbor-ip' (<IPv4 address>): 30.30.30.30
Value for 'endpoint-B interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'endpoint-B interface-main-id' (<string>): 0/0/0/2
Value for 'endpoint-B subinterface-id' (<unsignedInt>): 10
Value for 'endpoint-B p2p-name' (<string>): R3_to_R1_VLAN10

root@ncs(config-l2vpn-exam)# p2p-connection vlan20
Value for 'pw-id' (<unsignedInt>): 2020
Value for 'pw-class-name' (<string>): L2VPN-CLASS
Value for 'group-name' (<string>): L2VPN-GROUP
Value for 'vlan-id' (<unsignedShort>): 20
Value for 'endpoint-A device' [CE01,CE1,CE02,CE2,...]: R1
Value for 'endpoint-A pe-neighbor-ip' (<IPv4 address>): 10.10.10.10
Value for 'endpoint-A interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'endpoint-A interface-main-id' (<string>): 0/0/0/2
Value for 'endpoint-A subinterface-id' (<unsignedInt>): 20
Value for 'endpoint-A p2p-name' (<string>): R1_to_R3_VLAN20
Value for 'endpoint-B device' [CE01,CE1,CE02,CE2,...]: R3
Value for 'endpoint-B pe-neighbor-ip' (<IPv4 address>): 30.30.30.30
Value for 'endpoint-B interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'endpoint-B interface-main-id' (<string>): 0/0/0/2
Value for 'endpoint-B subinterface-id' (<unsignedInt>): 20
Value for 'endpoint-B p2p-name' (<string>): R3_to_R1_VLAN20

결과

 xconnect group L2VPN-GROUP
  p2p R1_to_R3_VLAN10
   interface GigabitEthernet0/0/0/2.10
   neighbor ipv4 30.30.30.30 pw-id 1010
    pw-class L2VPN-CLASS
   !      
  !       
  p2p R1_to_R3_VLAN20
   interface GigabitEthernet0/0/0/2.20
   neighbor ipv4 30.30.30.30 pw-id 2020
    pw-class L2VPN-CLASS
   !      
  !       
 !