We're trying to introduce routing capability into the MWCore either in a Visio or using the built-in router. So far, I've made a table router in Visio, but it's creating a memory-leak that is crashing the DMA, so I was wondering if there is a recommended way to do this?
On the Element, the router would just be moving Outputs/Inputs between their assigned Streams within a given MWEdge (Although we would only need to move the Outputs for our use case). Here is a diagram to demonstrate this with the topology:
As of now there are 3 main tables involved in this:
- [8700] Streams - List of all Streams on all Edges in the Core cluster
- [8701] Primary Key - {MWEdge.ID}/{Stream.ID}
- [8702] Display Key - Display Key = {MWEdge.Name}/{Stream.Name}
- [8704] MWEdge - MWEdge name
- [8705] MWEdge FK - MWEdge ID [ForeignKey]
- [9600] Sources - List of all Inputs across Core cluster
- [9601] Primary Key - {MWEdge.ID}/{Source.ID}
- [9602] Display Key - {MWEdge.Name}/{Stream.Name}/{Source.Name}
- [9624] Stream - Stream Name
- [9623] Stream FK - Stream Primary Key [ForeignKey]
- [9632] MWEdge - MWEdge name
- [8900] Outputs - List of all Inputs across Core cluster
- [8901] Primary Key - {MWEdge.ID}/{Output.ID}
- [8902] Display Key - {MWEdge.Name}/{Stream.Name}/{Output.Name}
- [8911] Stream - Stream Name
- [8908] Stream FK - Stream Primary Key [ForeignKey]
- [8911] MWEdge - MWEdge name
- [8910] MWEdge FK - MWEdge ID [ForeignKey]
When a route change happens, all that occurs behind-the-scenes is the Source/Output changes which Stream it is linked to.
To note, as of now the Stream & Stream FK parameters are not writable, however the TechEx MWCore API does support changing these values as long as it is an existing Stream on the same Edge. I've created an automation script that takes the EdgeID/OutputID/StreamID (and API token), makes the API request, and updates the Stream read Parameter in the Outputs table.
The question is: Is this something that can be accomplished with Dataminer's built-in Routing module? If not, what is preventing this? (We have an engineering contract and may be able to put in a request if it's something around this Protocol) Otherwise is there a more efficient or optimized way to do this in Visio than I'm doing it currently?
---
Below is what I'm currently doing that is not working, you don't need to read, but including here in case it is helpful to understand what we're looking for...
---
Here is a visual of what I have as of now for an idea of what I want to create. (This is what's causing the memory leak, which is also being investigated):
- A: SetVar FilterComboBox to select an MWEdge (Only include streams/outputs from this Edge in the router).
- B: Nested Children=ROW groups.
- Parent group is for the Streams table (8700) and contains each Row (From the "All" button to the right of the routing table).
- Inner group is for the Outputs table (8900) and contains a single table cell/router button (D).
- C: A single Children=ROW group for the Outputs table (8900), containing the output labels only.
- D: A router button, containing several fields:
- SetVar Shape, which adds the the selected Output/Stream index to the SelectedStream variable.
- A Highlight extended condition, which creates a thick green line around the button when it is present in SelectedStream.
- A Parameter field w/ a FillColor conditional that turns the button green when the Output is currently routed to the corresponding Stream.
- E: Button that triggers the below script.
- To note, for our specific setup, each Stream contains a single Source, so we don't need to worry about input routing.
- To pass multiple parameters to the script I use RegexReplace w/ delimiters in a SetVar to create something resembling a dictionary/hash-map in a variable called SelectedStream.
- Also there are several other details about this drawing, including the text (Source tags) under the Stream labels, that are retrieved via a SubscriptionFilter. As well as an "Overview" page containing all of the routable Edges with lists of their current routes (Using nested Children groups again).
Here is the automation script that runs when the TAKE CHANGES button is pressed:
/*
Include DLL references:
C:\Skyline DataMiner\ProtocolScripts\DllImport\newtonsoft.json\13.0.2\lib\net45\Newtonsoft.Json.dll
C:\Skyline DataMiner\ProtocolScripts\DllImport\system.net.http\4.3.4\lib\net46\System.Net.Http.dll
Requires Memory File w/ same name as Element, and one entry w/ "Token" as description and <API Token> as value.
Expects SelectedStreams to be an '&' delimited list of "edgeID/streamID<edgeID/outputID"
*/
namespace MWCoreRouteStreams
{
// v2.0.4
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Skyline.DataMiner.Automation;
/// <summary>
/// Represents a DataMiner Automation script.
/// </summary>
public class Script
{
private const string _inputURL = "<url>/api/mwedge/<edge>/output";
private const string _setVar = "SelectedStreams";
private const int _streamParamId = 8908; // Outputs table: StreamID
private const int _refreshParamId = 200; // Retrieve MWEdges button
private const int _refreshMsPause = 2000; // Delay before refreshing/// <summary>
/// The script entry point.
/// </summary>
/// <param name="engine">Link with SLAutomation process.</param>
public void Run(IEngine engine)
{
// Get static input data
var element = engine.GetDummy("CoreServer");
var token = /* Retrieve API Token */
if (!element.IsActive)
{
engine.Log("ERROR: Techex MWCore element is not active.");
return;
}
if (String.IsNullOrWhiteSpace(token))
{
engine.Log("ERROR: Invalid token.");
return;
}// Get and parse Visio input data
var edgeID = engine.GetScriptParam("EdgeID").Value;
var selectedStreams = engine.GetScriptParam("SelectedStreams").Value;
var paths = ParseSelected(edgeID, selectedStreams);engine.Log($"Edge: {edgeID}; Output: {outputID}; Stream {streamID}"); // DEBUG
// Update streams via API
using (var client = InitRequest(token))
{
var baseURL = _inputURL.Replace("<url>", element.PollingIP).Replace("<edge>", edgeID);foreach ( KeyValuePair<string, string> path in paths )
{
var param = $"{{\"stream\":\"{path.Value}\",\"paused\":false}}";
engine.Log($"{baseURL}/{path.Key} PUT {param}"); // DEBUGvar res = new JObject();
Task.Run(async () => { res = await Request(client, $"{baseURL}/{path.Key}", HttpMethod.Put, param); }).Wait();
engine.Log($"{baseURL}/{path.Key} Response: {res}"); // DEBUGif (res.ContainsKey("stream"))
{
var streamID = $"{edgeID}/{res["stream"]}";
element.SetParameterByPrimaryKey(_streamParamId, $"{edgeID}/{path.Key}", streamID);
}
}
}// Refresh Dataminer cache
Thread.Sleep(_refreshMsPause);
element.SetParameter(_refreshParamId, 1);
// Reset input selection session variable
engine.AddScriptOutput(UIVariables.VisualOverview.CreateKey(_setVar), "&");
}/// <summary>
/// Parse list of streams from Visio SessionVar 'SelectedStreams' (Returns formatted as {outputID: streamID})
/// </summary>
/// <param name="edgeID">ID of the Edge to filter by.</param>
/// <param name="selectedStreams">Visio session variable 'SelectedStreams.'</param>
private static Dictionary<string, string> ParseSelected(string edgeID, string selectedStreams)
{
string[] selected = selectedStreams.Split('&');
var paths = new Dictionary<string, string>();foreach (string path in selected)
{
if (path.Contains("<"))
{
string[] pathsplit = path.Split('<');
string[] outsplit = pathsplit[0].Split('/');
string[] insplit = pathsplit[1].Split('/');if (edgeID.Equals(outsplit[0]) & edgeID.Equals(insplit[0]))
{
paths.Add(outsplit[1], insplit[1]);
}
}
}return paths;
}/// <summary>
/// Instaniate a client for creating Http requests (Recommended in a using() block).
/// </summary>
/// <param name="token">API bearer token to include in header of all requests.</param>
/// <param name="timeout">Optional value in seconds to use before giving up on a request.</param>
private static HttpClient InitRequest(string token = "", int timeout = 5)
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback=
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};var client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(timeout);
if (!String.IsNullOrWhiteSpace(token))
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
}
return client;
}/// <summary>
/// Make a simple async HTTP Request, returning the response body as a string.
/// </summary>
/// <param name="client">Return value of InitRequest().</param>
/// <param name="url">Full URL of request.</param>
/// <param name="data">String value of JSON data to include in request body.</param>
/// <param name="method">HTTP Method to use (Default: GET).</param>
private static async Task<JObject> Request(HttpClient client, string url, HttpMethod method, string data = "", string contentType = "application/json")
{
// Build request
var request = new HttpRequestMessage(method, url);
var content = new StringContent(data, null, contentType);
request.Content = content;// Send the request/wait for response
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode(); // Raise error if it failed// Get the response as a string
var body = await response.Content.ReadAsStringAsync();// Return as a JObject
return JObject.Parse(body);
}
}
}
Here is a low-code application built to demonstrate integration capabilities: Techex MWCore | Catalog (dataminer.services).
The app functionality is explained in Techex - Tech Partner Integrations - DataMiner Dojo
Using this app as a starting point, you could build your router.
For additional information and to bounce ideas, feel free to contact our SME thomas.gunkel@skyline.be
Reach out to Thomas and schedule a discovery call. He should be able to guide you through building the solution you need (with or without our help). The existing application is indeed not a final product, but it might be a starting point.
Thank you, I have looked into this but it was not working for us. There are missing images and data so we haven’t really looked further into it. Would you recommend opening a support ticket about this or is that something Thomas might be helpful with?