In the previous posts, we outlined the use of Ansible in Network Automation and how to use NAPALM in multi-vendor networks for both Configuration deployment and Validation. Next, we outlined how to use both Ansible and NAPALM to perform Device provisioning and Validation for deploying new configuration related to infrastructure network ( not service related). In this final post, we will outline how to perform service provisioning and deployment using Ansible and NAPALM.

 

This post combines many of the techniques we outlined in the previous three posts regarding the configuration deployment, and state validation in order to deploy and validate a simple L3VPN service activation. The below diagram outline the high-level approach that we are going to use in order to provision and validate the deployment of any new L3VPN.

 

 

In this scenario, we are going to use only juniper vMX nodes in order to simulate the whole topology and the below diagram outline the network topology.

 

 

In the next section, we are going to outline all the above-outlined steps in order to achieve the service provisioning for the L3VPN service.

 

Service Data Model

The L3VPN service data model can be built using different approaches in order to provision a L3VPN in a service provider environment. In this setup, we opted to use a more standard approach using a service data model very similar to the standard data model for L3VPN described in  RFC8299 (Using YANG Data Model for L3VPN Service Delivery).

 

The RFC modeled the L3VPN service using the YANG Data model which can be transformed into different representation language (YAML, XML or JSON). We will use the YAML data format to represent this generic YANG model and render it in the YAML format. This process is done manually, however, it can also be done programmatically however for simplicity we will show the YAML rendered format.

Below is the Snipped of  the YAML rendered L3VPN service model

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

We can see from the above model that the L3VPN is described using two main constructs

  • Generic VPN parameters

This section contains all the generic parameters that describe the VPN like the vpn_name, Route-target and Route-distinguisher and the VPN type.

  • Sites for this VPN

This section has a list of all the sites under this VPN that need to be provisioned and each item in this list has all the required parameters like

  • PE node which the site is attached to.
  • Interface on this PE that this site is connected to
  • VLAN used
  • IP prefix
  • Routing protocol used (BGP in this scenario)

From this Service model, we have all the needed parameters in order to provision the L3VPN and we understand that we need to provision this service on two PE routers (vMX2 and vMX3), however as we explanined in a previous post that it is very hard to transform this generic service model into a per device configuration, thus we need to transform this generic serivce model into a node service model that we can easily combine with the JINJA2 template for the L3VPN (JunOS conifguration in this case) in order to deliver the exact per node configuration for each device.

 

The below snippet outline the per-node data model generated from the above service data model.

 

The below snippet outline the JINJA2 template for the L3VPN provisioning for JunOS.

 

Using the same techniques we outlined in the previous posts we can render the final device configuration for both nodes to get the final per node configuration that will be pushed by Ansible into the devices. The below snippet outlines the final rendered configuration for vMX2 router for this L3VPN service.

Resource Validation

After we have generated the required configuration and before deploying this configuration into the devices we should perform some sanity checks in order to validate that this configuration when it will be pushed to the device will take effect as well as it will not impact any other service (other L3VPN customers) running on these nodes. Thus we need to perform some validation tests in order to verify that the network is ready and that there is no impact if this configuration went active. Below are some tests that we will perform in order to validate that the resources are ready on the network as well as that there will be no impact on existing services.

More complicated checks can be performed like RT/RD overlapping or IP subnet overlapping however these kind of tests require a backend DB to cross check against, so the goal is just to illustrate the idea, however for a real world scenario more advanced tests should be carried out.

The input to this pre-validation is the node data model that we generate in the previous check, we connect to the devices that will be provisioned for this service and retrieve some outputs from it (like show interface) and compare it to the node service model to make sure that all the above check pass. We use an Ansible playbook to trigger this pre-validation step before service deployment and it is very similar to the validation script that we use in the previous post. The below snippet outline the Playbook that we used to do the pre-validation using network state gathered by NAPALM.

- name: Gather Network Resources
 gather_facts: no
 connection: local
 hosts: all
 tags: [ gather ]
 vars_files:
 - "./l3vpn-node.yml"
 tasks:
 - name: GET BGP and LLDP output
 napalm_get_facts:
 hostname: "{{ ansible_host }}"
 username: "{{ ansible_user }}"
 dev_os: "{{ dev_os }}"
 password: "{{ ansible_ssh_pass }}"
 optional_args:
 port: "{{ ansible_port }}"
 filter: 
 - interfaces_ip
 - interfaces
 when: inventory_hostname in nodes
 - set_fact: node={{nodes[inventory_hostname]}}
 when: inventory_hostname in nodes
 - set_fact: node_interfaces={{node|map(attribute='links')|list}}
 when: inventory_hostname in nodes
 - set_fact: node_vlans=''
 when: inventory_hostname in nodes
 - set_fact: node_vlans="{{ node_vlans|list + item.keys()}}"
 with_list: "{{node_interfaces}}"
 when: inventory_hostname in nodes

- name: Validate Interfaces are available on the router
 assert:
 that: item.split('.')[0] in hostvars[inventory_hostname].napalm_interfaces.keys()
 msg: |
 Interface {{item.split('.')[0]}} Is not available on {{inventory_hostname}}
 with_list: "{{node_vlans}}"
 when: inventory_hostname in nodes
 ignore_errors: true

- name: Validate all VPN Interface is operational
 assert:
 that: hostvars[inventory_hostname].napalm_interfaces[item.split('.')[0]].is_up == true
 msg: |
 Interface {{item.split('.')[0]}} Is DOWN on {{inventory_hostname}}
 with_list: "{{node_vlans}}"
 when: inventory_hostname in nodes
 ignore_errors: true

- name: Validate VLANs are not already used on the interfaces
 assert:
 that: item not in hostvars[inventory_hostname].napalm_interfaces_ip.keys()
 msg: |
 Interface {{item}} Is already used in {{inventory_hostname}}
 with_list: "{{node_vlans}}"
 when: inventory_hostname in nodes
 ignore_errors: true

Service Deployment

Once we made sure that this new service model is valid and all the resources are available to provision the service we move forward to deploy the configuration on the devices. We use another Ansible playbook very similar to the one we used in the 2nd post in order to push the devices using Ansible and NAPALM to each device in the service model. The below snippet outline the playbook that we used to push the configuration for this customer L3VPN provisioning task.

---
- hosts: localhost
 name: Create node data model
 gather_facts: no
 tags: [ model ]
 vars_files:
 - "{{model|default('l3vpn-model.yml')}}"
 tasks:
 - name: Create per-node data model from fabric data model
 template: src=l3vpn-service.j2 dest=./l3vpn-node.yml


- name: Generate Configuration for all routers
 gather_facts: no
 connection: local
 hosts: all
 tags: [ template ]
 tasks:
 - include_vars: "./l3vpn-node.yml"
 - file: path=l3vpn-{{common.vrf_name}}-config state=directory
 run_once: true
 - name: Generate Configuration
 template: src=conf-template/jnpr-l3vpn.j2 dest=l3vpn-{{common.vrf_name}}-config/{{inventory_hostname}}-l3vpn.txt
 when: "inventory_hostname in nodes.keys()"

## Third Play
# commit variable is used to control the play
# if commit=0 then we will not commit the changes we will only generate diff
# if commit=1 then we will commit the changes and generate diff
- name: push the configuration to the devices
 gather_facts: no
 connection: local
 hosts: all
 tags: [ deploy ]
 tasks:
 - include_vars: "./l3vpn-node.yml"
 - file: path=l3vpn-conf-diff state=directory
 run_once: true
 - name: load the configuration to the devices
 napalm_install_config:
 hostname: "{{ ansible_host }}"
 username: "{{ ansible_user }}"
 dev_os: "{{dev_os}}"
 password: "{{ ansible_ssh_pass }}"
 optional_args:
 port: "{{ ansible_port }}"
 config_file: l3vpn-{{common.vrf_name}}-config/{{inventory_hostname}}-l3vpn.txt
 commit_changes: "{{commit}}"
 diff_file: l3vpn-conf-diff/{{inventory_hostname}}-diff.txt
 when: "inventory_hostname in nodes.keys()"

 

Service Validation

Once the configuration is pushed into the devices we perform another validation step in order to verify that the configuration was pushed correctly and that the VPN service is working as expected, again any custom checks can be applied however we will use the following tests just for illustration.

 

 

We again take the node data model and compare it against the state of the network after the configuration is pushed (retrieve show commands from the provisioned devices) and validate that all the above test cases have passed. The playbook is very similar to the one used in the pre validation step and it was illustrated in the 3rd post.

 

Conclusion

In this post, we outlined how to use Ansible as an orchestration system for service deployment and how it can be used to build the data model for the service and how it can be used to deploy and validate the configuration for a L3VPN. This simple model can be extended to provide orchestration for other services and the validation can be as well extended to include other tests for service pre and post validation.Further, the overall process can be further automated and extended by providing a front-end to Ansible where the service parameters are entered there (Like vpn_name, RD/RT,etc..) and then these values are pushed into the service model, also a backend DB can be used to store this information so as it can be used for validation. All these enhancements can be used to deliver a carrier-grade service provisioning solution with Ansible in its core as outlined in this post.