on 01-25-2022 02:56 AM
Hi All,
Following my previous post and question which found a solution thanks to Mengbin From Cisco and the Developper community, i wanted to get deeper into the stacked service design because some of you might meet the same requirements in terms of service development. This article aims to provide you with some clues to build a stacked service.
This article relies on a lab i own which is composed of Juniper Network and Cisco System devices.
This design allows me to configure several routing-instances (aka Cisco VRF), several interfaces, several BGP peering sessions with external/internal Peers on several devices (Cisco and Juniper). It does not yet take into account the configuration of each BGP neighbor. This will be handled in a separate bottom package.
This example might be improved thanks to your welcomed comments
Above is the diagram of the design. It could have been divided in much more service/template components (aka Bottom Services). We could imagine of having a specific package to handle BGP, Routing policies templates for example. I will consider that new package in a future version of this stacked service.
Still, The Upper service "Create_l3vpn" is the result of the addition of all bottom packages.
Moreover, please note that some interactions between bottom services exists and might be mandatory, espacially between the NED and both Create_link and create_vrf packages.
Again, i'm still missing some technical skills which explains why i build my stacked service this way. BTW feel free to revert to me with your own advices and experiences.
Here is the yang model structure of the TOP Service package.
module create_l3vpn { namespace "http://my.example.com/create_l3vpn"; prefix create_l3vpn; import ietf-inet-types { prefix inet; } import tailf-ncs { prefix ncs; } import tailf-common { prefix tailf; } import create_link { prefix create_link; } import create_vrf { prefix create_vrf; } import tailf-ned-cisco-ios { prefix ios; } import junos { prefix junos; } organization "my.example.com"; description "Link template definition"; revision 2021-11-18 { description "Initial revision of this template."; } augment "/ncs:services" { list create_l3vpn { tailf:info "This is a CFS skeleton service"; description "This is a CFS skeleton service"; uses ncs:service-data; ncs:servicepoint "create_l3vpn"; key l3vpn_id; leaf l3vpn_id { tailf:info "Service Instance ID"; description "Service Instance ID"; type string {pattern "([0-9][0-9][0-9])";} } leaf ifjunos_vrf_type { tailf:info "Condition to be satisfied"; description "Condition to be satisfied"; type boolean; } leaf vrf_type { tailf:info "Vrf type for junos only"; description "Vrf type for junos only"; when "../ifjunos_vrf_type='true'"; type enumeration { enum vrf {description "Name Type of VRF";} } } list devices { tailf:info "List of devices enabled for this service"; description "List of devices enabled for this service"; key device; leaf device { tailf:info "Devices targeted for this service"; description "Devices targeted for this service"; type leafref {path "/ncs:devices/ncs:device/ncs:name";} } leaf device_type { tailf:info "Device type enabled for this service"; description "Device type enabled for this service"; type enumeration { enum Cisco {description "Device is a Cisco platform";} enum Juniper {description "Device is a Juniper platform";} } } container ios { tailf:info "Dedicated container to handle Cisco Interfaces"; description "Provide the requested components to handle Cisco Interfaces"; //when "contains(/ncs:devices/ncs:device[ncs:name=current()/../../devices/device]/ncs:device-type/ncs:cli/ncs:ned-id,'cisco-ios')"; when "../device_type='Cisco'"; tailf:cli-drop-node-name; list cisco_iface { tailf:info "List of GigabitEthernet interface"; description "List of GigabitEthernet interface"; key cisco_iface_id; leaf cisco_iface_id { tailf:info "GigabitEthernet Interface ID"; description "GigabitEthernet Interface ID"; mandatory true; type leafref {path "/ncs:devices/ncs:device[ncs:name=current()/../../../device]/ncs:config/ios:interface/ios:GigabitEthernet/ios:name";} } leaf cisco_iface_desc { tailf:info "GigabitEthernet Interface description"; description "GigabitEthernet Interface description"; type string; } leaf cisco_ipv4_address { tailf:info "Interface IPV4 address"; description "Interface IPV4 address"; type inet:ipv4-address; } leaf cisco_ipv4_submask { tailf:info "Interface IPV4 SubnetMask"; description "Interface IPV4 SubnetMask"; type inet:ipv4-address; } leaf cisco_vrf_name { tailf:info "Interface IPV4 SubnetMask"; description "Interface IPV4 SubnetMask"; type leafref {path "../../../../l3vpn_id";} } leaf cisco_iface_facing_device { tailf:info "Name of the facing Device For Iface description purpose"; description "Name of the facing Device For Iface description purpose"; type leafref { path "/ncs:devices/ncs:device/ncs:name"; } } } list bgp_cisco { tailf:info "List of parameters related to BGP"; description "List of parameters related to BGP"; key bgp_id; leaf bgp_id { tailf:info "Peer Group Name"; description "Peer Group Name"; type string; } leaf local_as { tailf:info "Local ASN"; description "Local ASN"; type enumeration { enum 65003 {description "Virtual routing forwarding instance";} } } list peer-session { tailf:info "List of parameters related to BGP Peer sessions"; description "List of parameters related to BGP Peer sessions"; key peer_ip; leaf peer_ip { tailf:info "Peer IP address"; description "Peer IP address"; type inet:ipv4-address; } leaf peer_as { tailf:info "Peer As Number"; description "Peer As Number"; type string; } } } } container junos { tailf:info "Dedicated container to handle Juniper Interfaces"; description "Provide the requested components to handle Juniper Interfaces"; //when "contains(/ncs:devices/ncs:device[ncs:name=current()/../../devices/device]/ncs:device-type/ncs:netconf/ncs:ned-id,'juniper-junos')"; when "../device_type='Juniper'"; tailf:cli-drop-node-name; list junos_iface { tailf:info "List of Junos interface"; description "List of Junos interface"; key junos_iface_id; leaf junos_iface_id { tailf:info "Junos Interface ID"; description "Junos Interface ID"; mandatory true; type leafref {path "/ncs:devices/ncs:device[ncs:name=current()/../../../device]/ncs:config/junos:configuration/junos:interfaces/junos:interface/junos:name";} } list junos_unit { tailf:info "List of Junos Unit ID"; description "List of Junos Unit ID"; key junos_vlan_id; leaf junos_vlan_id { tailf:info "Junos Vlan ID"; description "Junos Vlan ID"; type uint16 {range "1..4094";} } leaf junos_unit_desc { tailf:info "Junos Unit Description"; description "Junos Unit Description"; type string; } leaf junos_inet_ip { tailf:info "Junos Unit Ip address"; description "Junos Unit Ip address"; type tailf:ip-address-and-prefix-length; } leaf junos_iface_facing_device { tailf:info "Name of the facing Device For Iface description purpose"; description "Name of the facing Device For Iface description purpose"; type leafref { path "/ncs:devices/ncs:device/ncs:name"; } } } } list bgp_junos { tailf:info "List of parameters related to BGP"; description "List of parameters related to BGP"; key group_name; leaf group_name { tailf:info "Peer Group Name"; description "Peer Group Name"; type string; } leaf local_as { tailf:info "Local ASN"; description "Local ASN"; type enumeration { enum 65003 {description "Virtual routing forwarding instance";} } } leaf session_type { tailf:info "BGP session Type Internal/External for junos only"; description "BGP session Type Internal/External for junos only"; type enumeration { enum internal {description "IBGP session";} enum external {description "EBGP session";} } } list peer-session { tailf:info "List of parameters related to BGP Peer sessions"; description "List of parameters related to BGP Peer sessions"; key peer_ip; leaf peer_ip { tailf:info "Peer IP address"; description "Peer IP address"; type inet:ipv4-address; } leaf peer_as { tailf:info "Peer As Number"; description "Peer As Number"; type string; } } } } } container rd { tailf:info "Route Distinguisher related informations"; description "Route Distinguisher related informations"; leaf rd_id { tailf:info "RD ID info"; description "RD ID info"; type string {pattern "([0-9][0-9])";} } } } } }
As you can see from the above yang model, this example takes into account the diversity of devices composing the network. This is why i introduced some boolean leafs considering i have to choose between a Cisco or Juniper router and even both of them.
The various lists in this model are required to handle one or more instances of each specific items (peer-sessions ,peer neighbors, interfaces and so on...)
And then follows the associated template :
<config-template xmlns="http://tail-f.com/ns/config/1.0"> <services xmlns="http://tail-f.com/ns/ncs"> <create_link xmlns="http://my.example.com/create_link"> <link_id>jeynet-link-{/l3vpn_id}</link_id> <?foreach {/devices}?> <devices> <device>{device}</device> <?if {device_type ='Cisco'}?> <?foreach {ios/cisco_iface}?> <ios> <cisco_iface> <cisco_iface_id>{cisco_iface_id}</cisco_iface_id> <cisco_iface_desc>Lien vers {cisco_iface_facing_device}</cisco_iface_desc> <cisco_ipv4_address>{cisco_ipv4_address}</cisco_ipv4_address> <cisco_ipv4_submask>{cisco_ipv4_submask}</cisco_ipv4_submask> <cisco_vrf_name>svc-vpn-{cisco_vrf_name}</cisco_vrf_name> </cisco_iface> </ios> <?end?> <?end?> <?if {device_type ='Juniper'}?> <?foreach {junos/junos_iface}?> <junos> <junos_iface> <junos_iface_id>{junos_iface_id}</junos_iface_id> <?foreach {junos_unit}?> <junos_unit> <junos_vlan_id>{junos_vlan_id}</junos_vlan_id> <junos_unit_desc>Lien vers {junos_iface_facing_device}</junos_unit_desc> <junos_inet_ip>{junos_inet_ip}</junos_inet_ip> </junos_unit> <?end?> </junos_iface> </junos> <?end?> <?end?> </devices> <?end?> </create_link> <create_vrf xmlns="http://my.example.com/create_vrf"> <vrf_name>{/l3vpn_id}</vrf_name> <ifjunos_vrf_type>{/ifjunos_vrf_type}</ifjunos_vrf_type> <vrf_type>{/vrf_type}</vrf_type> <?foreach {/devices}?> <devices> <device>{device}</device> <?if {device_type ='Juniper'}?> <?foreach {junos/junos_iface}?> <junos_iface> <junos_iface_id>{junos_iface_id}</junos_iface_id> <?foreach {junos_unit}?> <cvlan_id>{junos_vlan_id}</cvlan_id> <?end?> </junos_iface> <?end?> <?end?> <junos_protocols> <?foreach {junos/bgp_junos}?> <bgp> <bgp_grp_name>{group_name}</bgp_grp_name> <local_as>{local_as}</local_as> <bgp_grp_type>{session_type}</bgp_grp_type> <?foreach {peer-session}?> <peer-sessions> <peer_ip>{peer_ip}</peer_ip> <peer_as>{peer_as}</peer_as> </peer-sessions> <?end?> </bgp> <?end?> </junos_protocols> </devices> <?end?> <devices> <device>{device}</device> <?if {device_type ='Cisco'}?> <cisco_protocols> <?foreach {ios/bgp_cisco}?> <bgp> <bgp_id>{bgp_id}</bgp_id> <local_as>{local_as}</local_as> <?foreach {peer-sessions}?> <peer-sessions> <peer_ip>{peer_ip}</peer_ip> <peer_as>{peer_as}</peer_as> </peer-sessions> <?end?> </bgp> <?end?> </cisco_protocols> <?end?> </devices> <rd> <rd_id>{/rd/rd_id}</rd_id> </rd> </create_vrf> </services> </config-template>
This XML template, is the result of the addition of the "service" part of each bottom service packages.
As an example, please find below an extract of the "create_link" bottom service template from which i picked up the service part and added it to my Top Service "create_l3vpn" template :
admin@ncs(config)# commit dry-run outformat xml result-xml { local-node { data <devices xmlns="http://tail-f.com/ns/ncs"> <device> <name>cisco03</name> <config> <interface xmlns="urn:ios"> <GigabitEthernet> <name>0/0/1</name> <description>cisco_iface_desc</description> <vrf> <forwarding>svc-vpn-001</forwarding> </vrf> <ip> <no-address> <address xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"/> </no-address> <address> <primary> <address>192.168.255.9</address> <mask>255.255.255.252</mask> </primary> </address> </ip> </GigabitEthernet> </interface> </config> </device> </devices>
<services xmlns="http://tail-f.com/ns/ncs"> <create_link xmlns="http://my.example.com/create_link"> <link_id>001</link_id> <devices> <device>cisco03</device> </devices> <ios> <cisco_interface> <cisco_iface_id>0/0/1</cisco_iface_id> <cisco_iface_desc>cisco_iface_desc</cisco_iface_desc> <cisco_ipv4_address>192.168.255.9</cisco_ipv4_address> <cisco_ipv4_submask>255.255.255.252</cisco_ipv4_submask> <cisco_vrf_name>svc-vpn-001</cisco_vrf_name> </cisco_interface> </ios> </create_link> </services>
The Bolded section of the above template is added to the top service template to build a full service meaning that you will have to repeat this operation for each of the bottom packages your top service relies on.
In my design i did this operation twice and added both "create_link" and "create_vrf" service part in my Top Service "create_l3vpn".
To help you in creating your own stacked service, please find below the full content of my bottom service "create_link" which relies as mentionned earlier on Cisco and Juniper Neds, especially to retrieve the corresponding interfaces for each kind of devices.
Here is the Yang model
module create_link { namespace "http://my.example.com/create_link"; prefix create_link; import ietf-inet-types { prefix inet; } import tailf-ncs { prefix ncs; } import tailf-common { prefix tailf; } import tailf-ned-cisco-ios { prefix ios; } import junos { prefix junos; } organization "my.example.com"; description "Link template definition"; revision 2021-11-18 { description "Initial revision of this template."; } augment "/ncs:services" { list create_link { tailf:info "This is an RFS skeleton service"; description "This is an RFS skeleton service"; key link_id; uses ncs:service-data; ncs:servicepoint "create_link"; leaf link_id { tailf:info "Link unique identifier"; description "Link unique identifier"; type string; } list devices { tailf:info "List of requested device to build the service"; description "List of requested device to build the service"; key device; leaf device { tailf:info "Device enabled for this service"; description "Device enabled for this service"; type leafref {path "/ncs:devices/ncs:device/ncs:name";} } //Both Container ios and Junos look at the NED ID to identify which type of Device the interface has to be configured for container ios { tailf:info "Dedicated container to handle Cisco Interfaces"; description "Provide the requested components to handle Cisco Interfaces"; when "contains(/ncs:devices/ncs:device[ncs:name=current()/../../devices/device]/ncs:device-type/ncs:cli/ncs:ned-id,'cisco-ios')"; tailf:cli-drop-node-name; list cisco_iface { tailf:info "List of requested Cisco Interfaces to build the service"; description "List of requested Cisco Interfaces to build the service"; key cisco_iface_id; leaf cisco_iface_id { tailf:info "GigabitEthernet Interface ID"; description "GigabitEthernet Interface ID"; type string; } leaf cisco_iface_desc { tailf:info "GigabitEthernet Interface description"; description "GigabitEthernet Interface description"; type string; } leaf cisco_ipv4_address { tailf:info "Interface IPV4 address"; description "Interface IPV4 address"; type string; } leaf cisco_ipv4_submask { tailf:info "Interface IPV4 SubnetMask"; description "Interface IPV4 SubnetMask"; type string; } leaf cisco_vrf_name { tailf:info "Name of the VRF associated to this Interface"; description "Name of the VRF associated to this Interface"; type string; } } } //Both Container ios and Junos look at the NED ID to identify which type of Device the interface has to be configured for container junos { tailf:info "Dedicated container to handle Juniper Interfaces"; description "Provide the requested components to handle Juniper Interfaces"; when "contains(/ncs:devices/ncs:device[ncs:name=current()/../../devices/device]/ncs:device-type/ncs:netconf/ncs:ned-id,'juniper-junos')"; tailf:cli-drop-node-name; list junos_iface { tailf:info "List of requested Cisco Interfaces to build the service"; description "List of requested Cisco Interfaces to build the service"; key junos_iface_id; leaf junos_iface_id { tailf:info "Junos Interface ID"; description "Junos Interface ID"; type string; } list junos_unit { tailf:info "List of requested Cisco Interfaces to build the service"; description "List of requested Cisco Interfaces to build the service"; key junos_vlan_id; leaf junos_vlan_id { tailf:info "Junos Vlan ID"; description "Junos Vlan ID"; type string; } leaf junos_unit_desc { tailf:info "Junos Unit Description"; description "Junos Unit Description"; type string; } leaf junos_inet_ip { tailf:info "Junos Unit Ip address"; description "Junos Unit Ip address"; type string; } } } } } } } }
and the associated XML Template :
<config-template xmlns="http://tail-f.com/ns/config/1.0" servicepoint="create_link"> <devices xmlns="http://tail-f.com/ns/ncs"> <?foreach {/devices}?> <device> <name>{device}</name> <config> <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm"> <interfaces> <?foreach {junos/junos_iface}?> <interface> <name>{junos_iface_id}</name> <flexible-vlan-tagging/> <native-vlan-id>{junos_unit/junos_vlan_id}</native-vlan-id> <?foreach {junos_unit}?> <unit> <name>{junos_vlan_id}</name> <description>{junos_unit_desc}</description> <vlan-id>{junos_vlan_id}</vlan-id> <family> <inet> <address> <name>{junos_inet_ip}</name> </address> </inet> </family> </unit> <?end?> </interface> <?end?> </interfaces> </configuration> </config> </device> <?end?> </devices> <devices xmlns="http://tail-f.com/ns/ncs"> <?foreach {/devices}?> <device> <name>{device}</name> <config> <interface xmlns="urn:ios"> <?foreach {ios/cisco_iface}?> <GigabitEthernet> <name>{cisco_iface_id}</name> <description>{cisco_iface_desc}</description> <vrf> <forwarding>{cisco_vrf_name}</forwarding> </vrf> <ip> <address> <primary> <address>{cisco_ipv4_address}</address> <mask>{cisco_ipv4_submask}</mask> </primary> </address> </ip> </GigabitEthernet> <?end?> </interface> </config> </device> <?end?> </devices> </config-template>
Again in this template, i take into account the diversity of devices composing my network, which implies to rely on specific NEDs for each type of device. Moreover, i take into account the fact of having multiple interfaces attached to the same VRF.
As mentioned in my previous post, i tried to manage complexity (if there is any) in my top service. However, i still had to handle some specifics in my bottom packages.
Some would say that easier the bottom packages are, the more reusable these services are. This is a good point of view, but it is sometimes not that obvious to apply.
In this design i had to handle lists, loops in all packages and not only in the top service.
Finally, i would say that the service is as complicated as you imagined it at the very beginning even if you tried to limit its complexity.
It is still not that bad to begin the building of a Multi-Vendor L3VPN network based on this template.
And because this service needs some improvments, feel free to comment this article so we can all take advantage of its existence.
Moreover, let me know if you want some more detailed informations.
Best Regards,
Jerems
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the NSO Developer community: