Bulk Eline Provisioning using Workflows
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
The following artifacts from ALED are required to run this tutorial:
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.
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.
Note
Text marked with ** indicate best practices
Navigate to the Artifacts page and use the Import & Install button to import the artifacts
Check that the import is successful and that no errors are reported
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:
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
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
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.
The bulkEline workflow takes the following as input parameters
The service object is as follows:
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
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>
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
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
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
The targets list uses a basic table list and presents the user with the following
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
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