L2VPN version5

변경사항

  • 필수 변수
    • pw-id
    • vlan-id
    • endpoint device
    • device neighbor ip 및 적용 interface
    • group-name
  • 공통변수
    • p2p-connection-name = (pw-class-name, p2p-name)
    • vlan-id = (subinterface-id)
  • 서비스 구성 계층도
    l2vpn <서비스 이름>
    ├─ group-name <그룹 이름>
    ├─ endpoint-A device <장비 이름>
    ├─ endpoint-A pe-neighbor-ip <IP 주소>
    ├─ endpoint-B device <장비 이름>
    └─ endpoint-B pe-neighbor-ip <IP 주소>
        │
        └─ p2p-connection
            ├─ p2p-connection <연결 이름>
            │   ├─ pw-id <ID>
            │   ├─ vlan-id <ID>
            │   ├─ encapsulation-type <캡슐화 타입>
            │   ├─ endpoint-A interface <인터페이스 타입> <ID>
            │   └─ endpoint-B interface <인터페이스 타입> <ID>
            │
            └─ p2p-connection <다른 연결 이름>
                ├─ pw-id <ID>
                ├─ vlan-id <ID>
                ├─ encapsulation-type <캡슐화 타입>
                ├─ endpoint-A interface <인터페이스 타입> <ID>
                └─ endpoint-B interface <인터페이스 타입> <ID>
  • 변경사항
    • 기존에는 하나의 그룹에 여러 개가 불가능했지만 현재는 같은 그룹에 여러 P2P 추가 가능
  • 기존
    Value for 'pw-id' (<unsignedInt>): 1030
    Value for 'vlan-id' (<unsignedShort>): 100
    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-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
    root@ncs(config-p2p-connection-exam)# commit
  • 현재
    root@ncs(config)# l2vpn test 
    Value for 'group-name' (<string>): tt
    Value for 'endpoint-A device' [CE01,CE1,CE02,CE2,...]: R1
    Value for 'endpoint-A pe-neighbor-ip' (<IPv4 address>): 30.30.30.30
    Value for 'endpoint-B device' [CE01,CE1,CE02,CE2,...]: R3
    Value for 'endpoint-B pe-neighbor-ip' (<IPv4 address>): 10.10.10.10
    root@ncs(config-l2vpn-test)# p2p-connection test1
    Value for 'pw-id' (<unsignedInt>): 1030
    Value for 'vlan-id' (<int, 1 .. 4000>): 100
    Value for 'encapsulation-type' [default,dot1q,untagged]: dot1q
    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-B interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
    Value for 'endpoint-B interface-main-id' (<string>): 0/0/0/2
    root@ncs(config-p2p-connection-test1)# commit 
    Commit complete.
    root@ncs(config-p2p-connection-test1)# exit
    root@ncs(config-l2vpn-test)# p2p-connection test2 
    Value for 'pw-id' (<unsignedInt>): 1031
    Value for 'vlan-id' (<int, 1 .. 4000>): 200
    Value for 'encapsulation-type' [default,dot1q,untagged]: untagged
    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-B interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
    Value for 'endpoint-B interface-main-id' (<string>): 0/0/0/2
    root@ncs(config-p2p-connection-test2)# exit
    root@ncs(config-l2vpn-test)# p2p-connection test3
    Value for 'pw-id' (<unsignedInt>): 1032
    Value for 'vlan-id' (<int, 1 .. 4000>): 11 
    Value for 'encapsulation-type' [default,dot1q,untagged]: dot1q
    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-B interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
    Value for 'endpoint-B interface-main-id' (<string>): 0/0/0/2


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-12 {
    description "Refactored to define common endpoint data at the top level.";
  }

  grouping L2VPN-ENDPOINT-COMMON {
    leaf device {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      mandatory true;
      description "The PE device for this endpoint.";
    }
    leaf pe-neighbor-ip {
      type inet:ipv4-address;
      mandatory true;
      description "The IP address of this PE router for peering (e.g., Loopback IP).";
    }
  }

  grouping L2VPN-ENDPOINT-CONNECTION-DETAILS {
    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; }
    leaf p2p-name { type string; }
    leaf mtu { type uint16; }

    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 int32 { range "1..4000"; }
        mandatory true;
      }
    }
  }

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

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

    leaf group-name {
      type string;
      mandatory true;
    }

    container endpoint-A {
      uses L2VPN-ENDPOINT-COMMON;
    }

    container endpoint-B {
      uses L2VPN-ENDPOINT-COMMON;
    }

    list p2p-connection {
      key "connection-name";
      description "A list of individual P2P connections between the two endpoints defined above.";

      leaf connection-name { type string; }
      leaf pw-id { type uint32; mandatory true; }
      leaf pw-class-name { type string; }
      leaf control-word { type boolean; default "false"; }
      leaf vlan-id {
        type int32 {
          range "1..4000";
        }
        mandatory true;
      }
      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).";
      }

      container endpoint-A {
        uses L2VPN-ENDPOINT-CONNECTION-DETAILS;
      }

      container endpoint-B {
        uses L2VPN-ENDPOINT-CONNECTION-DETAILS;
      }
    }
  }
}

l2vpn.py

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 called for l2vpn: {service.name}")
        template = ncs.template.Template(service)

        for connection in service.p2p_connection:
            self.log.info(f"Processing connection: {connection.connection_name}")
            tvars_a = self.get_tvars(root, service, connection, 'A')
            template.apply('l2vpn-template', tvars_a)

            tvars_b = self.get_tvars(root, service, connection, 'B')
            template.apply('l2vpn-template', tvars_b)

    def get_tvars(self, root, service, conn, endpoint_id):
        tvars = ncs.template.Variables()
        if endpoint_id == 'A':
            common_ep = service.endpoint_A
            conn_ep = conn.endpoint_A
        else:
            common_ep = service.endpoint_B
            conn_ep = conn.endpoint_B


        tvars.add('XCONNECT_GROUP_NAME', service.group_name)
        tvars.add('PW_ID', conn.pw_id)
        tvars.add('CONTROL_WORD_ENABLED', 'true' if conn.control_word else 'false')
        tvars.add('L2_SERV_VLAN_ID', conn.vlan_id)
        tvars.add('L2_SERV_ENCAP', conn.encapsulation_type)

        tvars.add('PW_CLASS_NAME', conn.pw_class_name if conn.pw_class_name else conn.connection_name)
        tvars.add('P2P_NAME', conn_ep.p2p_name if conn_ep.p2p_name else conn.connection_name)
        tvars.add('PW_CLASS_REF', conn_ep.p2p_name if conn_ep.p2p_name else conn.connection_name)
        
        device_name = common_ep.device
        tvars.add('DEVICE', device_name)
        tvars.add('NEIGHBOR_IP', common_ep.pe_neighbor_ip)

        iface_type_str = str(conn_ep.interface_type)
        iface_main_id = conn_ep.interface_main_id
        sub_id = conn_ep.subinterface_id if conn_ep.subinterface_id else conn.vlan_id
        
        full_subif_name = f"{iface_type_str}{iface_main_id}.{sub_id}"

        tvars.add('INTERFACE_TYPE', iface_type_str)
        tvars.add('INTERFACE_ID', f"{iface_main_id}.{sub_id}")
        tvars.add('FULL_INTERFACE_NAME', full_subif_name)

        mtu_value = conn_ep.mtu if conn_ep.mtu else 1522 
        try:
            full_if_name = f"{iface_type_str}{iface_main_id}"
            self.log.info(f"Attempting to read MTU for {device_name}:{full_if_name}")
            interface_config = root.devices.device[device_name].config.interface
            phy_if = interface_config[iface_type_str][iface_main_id]

            if phy_if.mtu is not None:
                mtu_value = phy_if.mtu
                self.log.info(f"SUCCESS: Read existing MTU {mtu_value} from {device_name}:{full_if_name}")
            else:
                 self.log.info(f"INFO: MTU not configured on {device_name}:{full_if_name}. Using value from service: {mtu_value}.")
        except Exception as e:
            self.log.info(f"WARN: Could not read device MTU for {device_name}:{full_if_name}. Using value from service: {mtu_value}. Exception: {e}")
        tvars.add('MTU', mtu_value)


        l2_serv_ingress_rewrite_type = 'none'
        l2_serv_ingress_rewrite_vlan = 0
        if hasattr(conn_ep, 'rewrite_policy'):
            rewrite_policy = conn_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)

        self.log.info(f"Generated TVARS for {device_name} ({endpoint_id}): {tvars}")
        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>