Bulk Eline Provisioning using Workflows

Tutorials

Bulk Eline Provisioning using Workflows

Overview

This tutorial leverages the power of Workflows to easily automate the bulk provisioning of Eline services. The concepts described below can easily be applied to other service types.

Note

This tutorial has been tested with and is supported in NSP 25.8

Artifacts

The following artifacts from ALED are required to run this tutorial:

  • service-mgt-artifacts-common
  • service-mgt-artifacts-unified

Prerequisites

This tutorial requires at least two SROS based network elements that have been discovered by NSP. These can be model mode or classically managed devices.

There must be a MPLS topology connecting the nodes, including LSPs and Paths. The examples require that the hardware port 1/1/10 are available on both nodes and can be configured as ethernet access ports with Dot1Q encapsulation.

Disclaimer

This tutorial is meant as a proof of concept and as an example method for configuring a service. It is not designed to be implemented, and must not be used as is, in a production network. It is intended to guide development of an OSS.

Tutorial

Note

Text marked with ** indicate best practices

Workflows Best Practices

Import Artifacts

Navigate to the Artifacts page and use the Import & Install button to import the artifacts

  • service-mgt-artifacts-common
  • service-mgt-artifacts-unified

Check that the import is successful and that no errors are reported

Create required Service Template

This tutorial makes use of epipe predefined intent type

This is imported automatically into Network Intents when the previous artifacts are loaded. A corresponding Service Template named epipe must exist prior to executing this tutorial

To create the Service Template:

  • navigate to Service Management and go to the Service Templates section using the pull down menu.
  • Use + Service template button to create a template named epipe with service intent type epipe using intent version 2 and the default config form
  • Select Create to save the template

Workflow

Use the following YAML to create the workflow. This can be done either by using the vsCode plugin or by navigating to the Workflows page and creating the workflow manually.

To create the workflow manually

  • Navigate to the Workflows page and use the pull down menu to go to the Workflows section
  • Use the + Workflow button and paste the following YAML
version: '2.0'

bulkEline:
  workflow_meta:
    title: Bulk Eline
    author: Nokia DXS
    version: "1.0.0"
    deprecated: false
    dependencies:
      platform:
        nspOS: ['25.8']
      nodal:
        - type: 'Nokia 7x50'
          releases: ['23.X', '24.X', '25.X']
          mgmtmode: ['classic', 'model-driven']



  input:
    - templateName
    - targets

  output:
    result: <% $.result %>

  output-on-error:
    result: failed

  tasks:
    setVars:
      action: std.noop
      publish:
        rcFind: https://restconf-gateway/restconf/operations/nsp-inventory:find
        ibsfCreate: https://<% locate_nsp() %>:8545/restconf/data/nsp-service-intent:intent-base
      on-success:
        - getIntentType

    getIntentType:
      action: nsp.https
      input:
        method: POST
        url: <% $.rcFind %>
        body:
          input:
            xpath-filter: /service-template:templates/template[name='<% $.templateName %>']
            fields: name;description;intent-type;intent-version;state
            include-meta: false
        resultFilter: $.content.get("nsp-inventory:output")
      publish:
        intentType: <% task().result.content.data.first().get("intent-type") %>
        intentTypeVersion: <% task().result.content.data.first().get("intent-version") %>
      on-success:
        - getCustomerId

    getCustomerId:
      with-items: item in <% $.targets %>
      concurrency: 10
      action: nsp.https
      input:
        method: POST
        url: <% $.rcFind %>
        body:
          input:
            xpath-filter: /nsp-customer:customers/customer[name='<% $.item.custName %>']
            fields: name;id;description
            include-meta: false
        resultFilter: $.content.get("nsp-inventory:output")
      publish:
        customerIds: <% task().result.content.data.select($.id).flatten() %>
      on-success:
        - createElineService
    
    createElineService:
      with-items: 
        - cId in <% $.customerIds %>
        - item in <% $.targets %>
      concurrency: 10
      action: nsp.https
      input:
        method: POST
        url: <% $.ibsfCreate %>
        body:
          nsp-service-intent:intent:
            - template-name: <% $.templateName %>
              intent-type: <% $.intentType %>
              intent-type-version: <% $.intentTypeVersion %>
              service-name: <% $.item.svcName %>
              olc-state: deployed
              intent-specific-data:
                epipe:epipe:
                  customer-id: <% $.cId %>
                  ne-service-id: <% $.item.svcId %>
                  description: Bulk Provisioning using WFM
                  admin-state: unlocked
                  mtu: 1500
                  site-a:
                    device-id: <% $.item.siteA %>
                    endpoint:
                      - port-id: <% $.item.portA %>
                        description: Endpoint A
                        inner-vlan-tag: -1
                        outer-vlan-tag: <% $.item.svcId %>
                  site-b:
                    device-id: <% $.item.siteB %>
                    endpoint:
                      - port-id: <% $.item.portB %>
                        description: Endpoint B
                        inner-vlan-tag: -1
                        outer-vlan-tag: <% $.item.svcId %>
                  sdp-details:
                    sdp:
                      - source-device-id: <% $.item.siteA %>
                        destination-device-id: <% $.item.siteB %>
                        sdp-id: '<% $.item.sdpAB %>'
                        admin-state: unlocked
                        override-vc-id: false
                        vc-type: ether
                      - source-device-id: <% $.item.siteB %>
                        destination-device-id: <% $.item.siteA %>
                        sdp-id: '<% $.item.sdpBA %>'
                        admin-state: unlocked
                        override-vc-id: false
                        vc-type: ether
      publish:
        taskId: <% task().id %>
        createResponse: <% task().result.select($.content.response.data) %>
      on-success:
        - captureCreationTime

    captureCreationTime:
      wait-before: 5
      action: nsp.https
      input:
        method: GET
        url: https://workflow-manager/wfm/api/v1/action-execution/task/<% $.taskId %>
      publish:
        startTime: <% task().result.content.response.data.first().created_at %>    
        endTime: <% task().result.content.response.data.first().updated_at %>
      on-success:
        - extractTimeDifference

    extractTimeDifference:
      action: nsp.python
      input: 
        context: <% [$.startTime, $.endTime] %>
        script: |
          from datetime import datetime

          date_format = "%Y-%m-%d %H:%M:%S"
          t1 = datetime.strptime(context[0], date_format)
          t2 = datetime.strptime(context[1], date_format)
          delta = t2 - t1
          diff = delta.total_seconds()

          return round(diff);
      publish:
        execTime: <% task().result %>
      on-success:
        - captureServiceState

    captureServiceState:
      with-items: item in <% $.targets %>
      concurrency: 10
      action: nsp.https
      input:
        method: POST
        url: <% $.rcFind %>
        body:
          input:
            xpath-filter: /nsp-service:services/service-layer/eline[service-id='<% $.item.svcName %>']
        resultFilter: >-
          $.content.get("nsp-inventory:output").data.select({
          svcName => $.name, 
          svcId => $.get("ne-service-id"), 
          slcState => $.get("service-extension:eline-svc").get("slc-state"), 
          misaligned => $.get("service-extension:eline-svc").misaligned})
      publish:
        result: <% resolve_jinja(fetch_jinja("serviceState"), {execTime => $.execTime, data => task().result.content.flatten()}) %>

Click Validate & Update Flow button and then click the Create button

Next we will add a README to the workflow so that users are aware of its use**

Select View info From the 3 dots menu of the bulkEline workflow

To add the README select the Pencil icon on the workflow Readme tab and enter the following markdown text and then click Update

# Bulk Eline Provisioning using Workflows
This workflow leverages the power of Workflows to easily automate the bulk provisioning of Eline services.

Please see the tutorial on the NSP Developer Portal 

## Input

The bulkEline workflow takes the following as input parameters

*templateName* - The Service Management template to use

*targets* - a list of service objects to create

The service object is as follows:

+ *custName* - The customer
+ *svcId* - Service Id
+ *svcName* - Service Name
+ *siteA* - The NE id for the A end of the Eline
+ *portA* - The termination port at the A end of the Eline
+ *siteB* - The NE id for the B end of the Eline
+ *portB* - The termination port at the B end of the Eline
+ *sdpAB* - The service tunnel from A to B
+ *sdpBA* - The service tunnel from B to A

Code Walk Through

The bulkEline workflow can be used to automatically create a number of eline services using a https POST request to the Service Management API.

The time taken to provision the services is calculated and the results are displayed in a user friendly HTML format.

Input

The bulkEline workflow takes the following as input parameters

  • templateName - The Service Management template to use
  • targets - a list of service objects to create

The service object is as follows:

  • custName - The customer
  • svcId - Service Id
  • svcName - Service Name
  • siteA - The NE id for the A end of the Eline
  • portA - The termination port at the A end of the Eline
  • siteB - The NE id for the B end of the Eline
  • portB - The termination port at the B end of the Eline
  • sdpAB - The service tunnel from A to B
  • sdpBA - The service tunnel from B to A

An example input is shown below

templateName: epipe
targets:
  - custName: Default customer
    svcId: '1001'
    svcName: epipe_1001
    siteA: 92.168.97.183
    portA: Port 1/1/10
    siteB: 92.168.97.34
    portB: Port 1/1/10
    sdpAB: '2'
    sdpBA: '1'
  - custName: Default customer
    svcId: '1000'
    siteA: 92.168.97.34
    portA: Port 1/1/10
    siteB: 92.168.97.183
    portB: Port 1/1/10
    sdpAB: '1'
    sdpBA: '2'
    svcName: epipe_1000

Workflow Tasks

bulkEline Flow

Task: setVars

This task is the starting point for the workflow and is used to define and acquire the RESTCONF URIs that are used by other tasks in the workflow. They are defined in a task to reduce the potential of the operation failing due to latency**

Task: getIntentType

This task uses the nsp.https action to retrieve the service template information from Service Management. The fields and include-meta parameters are set to ensure that only the required data is retrieved**

Task: getCustomerId

This task uses the nsp.https action to retrieve the customer information from RESTCONF. The fields and include-meta parameters are set to ensure that only the necessary data is retrieved**
This task makes use of a with-items loop to process all of the objects in the targets list The concurrency parameter has been defined to constrain the number of parallel requests that the workflow executes. This is to ensure that the workflow does not consume all of the available resources**

Task: createElineService

This task uses the nsp.https action to invoke a service creation operation through the Service Management API. This task makes use of a with-items loop to process all of the objects in the targets list The concurrency parameter has been defined to constrain the number of parallel requests that the workflow executes. This is to ensure that the workflow does not consume all of the available resources**

Task: captureCreationTime

This task uses the nsp.https action to retrieve the start and end timestamps for the createElineService task execution from Workflows. A wait-before policy is used to instruct the workflow engine to wait a number of seconds before starting the task. This is to ensure that the createElineService task has completed before retrieving the results

Task: extractTimeDifference

This task uses a python action** to convert the start and end timestamps into datetime objects and then the delta between the two objects is calculated. Only the timestamps are passed in the task context to reduce the amount of data stored in the Workflow database**

Task: captureServiceState

This task uses the nsp.https action to query the service state in the RESTCONF service model. This task makes use of a with-items loop to process all of the objects in the targets list The concurrency parameter has been defined to constrain the number of parallel requests that the workflow executes. This is to ensure that the workflow does not consume all of the available resources**
This task uses a resultFilter to reduce the amount of data that is stored in the Workflows database**
The task uses a Jinja2 template serviceState to format the results using a HTML table

Jinja2 Template

Before the workflow can be executed the Jinja2 template serviceState must be imported into the Jinja2 templates page of the Workflows app.

Use the + Jinja Template button to create a template named serviceState using the following template

<!DOCTYPE html>
<html>
  <head>
    <style>
      table {font-family: NokiaPure, sans-serif;border-collapse: collapse;width: 100%;}
      td, th {border: 1px solid #dddddd;text-align: left;padding: 8px;}
      tr:nth-child(even) {background-color: transparent;}
      lime {margin-left: 19px;height: 12px;width: 12px;background-color: lime;border-radius: 50%;display: inline-block;}
      red {margin-left: 19px;height: 12px;width: 12px;background-color: red;border-radius: 50%;display: inline-block;}
    </style>
  </head>
  <body>
    <br/><span style="font-size: 1em; font-weight: 600; margin:10px; color:#183693 ;font-family: NokiaPure, sans-serif;">Service State</span><br/><br/>
    <div style="padding-right: 10px;">
      <table>
        <tr>
          <th>Service ID</th>
          <th>Service Name</th>
          <th>SLC State</th>
          <th>Misaligned</th>
        </tr>
        {% for item in data %}<tr>
          <td>{{ item.svcId }}</td> 
          <td>{{ item.svcName }}</td>
          <td>{{ item.slcState }}</td>
          <td>{{ item.misaligned }}</td>
        </tr>{% endfor %}
        <tr><td colspan="4">Overall Service Creation Time: {{ execTime }} sec(s)</td></tr>
      </table>
    </div>
  </body>
</html>

Executing the Workflow

Prior to executing the workflow, it must be in a published state.

Use the Modify state button from the workflow info page to set it to Published

At this point the workflow can be executed manually by supplying the complete input YAML /JSON or alternatively can be executed using the Workflows REST API

POST https://<NSP IP>/wfm/api/v1/execution
content-type: application/json

{
    "workflow_id": "bulkEline",
    "input": {
        "templateName": "epipe",
        "targets": [{
                "custName": "Default customer",
                "svcId": "1001",
                "svcName": "epipe_1001",
                "siteA": "92.168.97.183",
                "portA": "Port 1/1/10",
                "siteB": "92.168.97.34",
                "portB": "Port 1/1/10",
                "sdpAB": "2",
                "sdpBA": "1"
            }, {
                "custName": "Default customer",
                "svcId": "1000",
                "siteA": "92.168.97.34",
                "portA": "Port 1/1/10",
                "siteB": "92.168.97.183",
                "portB": "Port 1/1/10",
                "sdpAB": "1",
                "sdpBA": "2",
                "svcName": "epipe_1000"
            }
        ]
    },
    "params": {
        "env": "DefaultEnv"
    },
    "output": {},
    "notifyKafka": true
}

On a successful execution the following output is shown in the quick view output

bulkEline_output

Input Form (schemaForm)

To allow the workflow to be easily executed from the NSP UI, it can be enhanced with the addition of an Input Form. This input form uses schemaForm to quickly build an interactive UI that uses RESTCONF callouts** to retrieve the required data that the user can then select.

To create the Input Form, navigate to the Input Form section of the workflow and enter the following YAML and update the Input Form

type: object
properties:
  - name: templateName
    title: Template Name
    readOnly: false
    columnSpan: 2
    required: true
    newRow: true
    type: string
    component: autoComplete
    restCallout:
      url: /restconf/operations/nsp-inventory:find
      method: POST
      input:
        xpath-filter: /service-template:templates/template
        fields: name;description;intent-type;intent-version;state
        sort-by:
          - name
      output:
        key: name
        responsePathToData:
          - nsp-inventory:output
          - data
  - name: targets
    type: list
    title: Targets
    newRow: true
    columnSpan: 7
    required: true
    properties:
      - name: custName
        title: Customer Name
        type: string
        newRow: true
        columnSpan: 2
        required: true
        component: autoComplete
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: /nsp-customer:customers/customer
            fields: id;name;description
            sort-by:
              - name
          output:
            key: name
            responsePathToData:
              - nsp-inventory:output
              - data
      - name: svcId
        title: NE Service ID
        type: string
        newRow: true
        columnSpan: 2
        required: true
      - name: svcName
        title: Service Name
        type: string
        newRow: false
        columnSpan: 2
        required: true
      - name: siteA
        columnSpan: 2
        title: Site A
        type: string
        newRow: true
        required: true
        component: autoComplete
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: /nsp-equipment:network/network-element
            fields: ne-id
          output:
            key: ne-id
            responsePathToData:
              - nsp-inventory:output
              - data
      - name: portA
        columnSpan: 2
        newRow: false
        required: true
        type: leafref
        title: Port A
        displayKey: name
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: >-
              /nsp-equipment:network/network-element[ne-id='{%0}']/hardware-component/port
            substitutionList:
              - siteA
          output:
            responsePathToData:
              - nsp-inventory:output
              - data
        componentProps:
          rowID: name
        dependsOn: siteA
        properties:
          - name: name
            title: Name
            type: string
          - name: description
            title: Description
            type: string
      - name: siteB
        columnSpan: 2
        title: Site B
        type: string
        newRow: true
        required: true
        component: autoComplete
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: /nsp-equipment:network/network-element
            fields: ne-id
          output:
            key: ne-id
            responsePathToData:
              - nsp-inventory:output
              - data
      - name: portB
        columnSpan: 2
        newRow: false
        required: true
        type: leafref
        title: Port B
        displayKey: name
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: >-
              /nsp-equipment:network/network-element[ne-id='{%0}']/hardware-component/port
            substitutionList:
              - siteB
          output:
            responsePathToData:
              - nsp-inventory:output
              - data
        componentProps:
          rowID: name
        dependsOn: siteB
        properties:
          - name: name
            title: Name
            type: string
          - name: description
            title: Description
            type: string
      - name: sdpAB
        columnSpan: 2
        newRow: true
        required: true
        type: leafref
        title: SDP A to B
        displayKey: id
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: >-
              /nsp-service:services/tunnel-layer/mpls-tunnel[source-ne-id='{%0}'][destination-ne-id='{%1}']
            fields: name;description;source-ne-id;destination-ne-id;id
            substitutionList:
              - siteA
              - siteB
          output:
            responsePathToData:
              - nsp-inventory:output
              - data
        componentProps:
          rowID: name
        dependsOn: siteA
        properties:
          - name: id
            title: ID
            type: string
          - name: name
            title: Name
            type: string
      - name: sdpBA
        columnSpan: 2
        newRow: false
        required: true
        type: leafref
        title: SDP B to A
        displayKey: id
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: >-
              /nsp-service:services/tunnel-layer/mpls-tunnel[source-ne-id='{%0}'][destination-ne-id='{%1}']
            fields: name;description;source-ne-id;destination-ne-id;id
            substitutionList:
              - siteB
              - siteA
          output:
            responsePathToData:
              - nsp-inventory:output
              - data
        componentProps:
          rowID: name
        dependsOn: siteB
        properties:
          - name: id
            title: ID
            type: string
          - name: name
            title: Name
            type: string

The following Form will be generated

bulkEline Input Form

This makes use of RESTCONF callouts to interact with NSP to retrieve the necessary data to construct the required input values.

For example the templateName attribute is retrieved by using an autoComplete picker to execute a nsp-inventory find request. Note the fields attribute is used to reduce the amount of retrieved data**

name: templateName
    title: Template Name
    readOnly: false
    columnSpan: 2
    required: true
    newRow: true
    type: string
    component: autoComplete
    restCallout:
      url: /restconf/operations/nsp-inventory:find
      method: POST
      input:
        xpath-filter: /service-template:templates/template
        fields: name;description;intent-type;intent-version;state
        sort-by:
          - name
      output:
        key: name
        responsePathToData:
          - nsp-inventory:output
          - data

This is presented as a drop down list that the user can select from

bulkEline Input Form templateName

The targets list uses a basic table list and presents the user with the following

bulkEline_inputForm_createTarget

The NE Service ID and Service Name use simple string objects.

The Customer Name, Site A and Site B objects use autoComplete pickers to retrieve the necessary data using RESTCONF callouts.

The Port A, Port B, SDP A to B and SDP B to A objects use leaf-ref pickers

name: portA
        columnSpan: 2
        newRow: false
        required: true
        type: leafref
        title: Port A
        displayKey: name
        restCallout:
          url: /restconf/operations/nsp-inventory:find
          method: POST
          input:
            xpath-filter: >-
              /nsp-equipment:network/network-element[ne-id='{%0}']/hardware-component/port
            substitutionList:
              - siteA
          output:
            responsePathToData:
              - nsp-inventory:output
              - data
        componentProps:
          rowID: name
        dependsOn: siteA
        properties:
          - name: name
            title: Name
            type: string
          - name: description
            title: Description
            type: string

leaf-ref pickers generate a list that the user can select from

bulkEline_inputForm_leafRef

In addition, they also use a substitution list that allows the callouts to use data already stored in other objects.

For example Port A uses the value stored in Site A as part of the xpath-filter

xpath-filter: >-
              /nsp-equipment:network/network-element[ne-id='{%0}']/hardware-component/port
substitutionList:
  - siteA

SDP A to B and SDP B to A objects use multiple substitution list entries to construct the xpath-filter

xpath-filter: >-
       /nsp-service:services/tunnel-layer/mpls-tunnel[source-ne-id='{%0}'][destination-ne-id='{%1}']
 fields: name;description;source-ne-id;destination-ne-id;id
 substitutionList:
   - siteA
   - siteB

The workflow can now be easily executed using the generated form. This ensures that the correct data is entered into each input field

Conclusions

  • Workflows can easily and quickly automate repetitive tasks
  • Input Forms allow the workflow to be easily integrated into the NSP ecosystem

On this page