Feature Routing - How does it work?

Created by Stuart Cowen, Modified on Thu, 22 Jun, 2023 at 6:38 PM by Stuart Cowen

What is a feature?

A feature is an installable piece of software that can be dragged into a node group or cluster in the design studio.

What is a route?

A route is a connection between two features. Routes are drawn so that features can communicate with one another. In the example below, the reason why filebeatmqtt feature has a route to elasticsearch is that it stores events there. Therefore, elasticsearch feature needs to be aware of an incoming connection and configure itself for it. See example 1 below:

What is an internal route?

  • An internal route is one between two features in the architecture. One of the features is the ingress feature and the other one is the egress feature.

  • In example 1 above, filebeatmqtt is the egress feature and elasticsearch is the ingress feature.

  • The code snippets below show manifest.yaml files of the participating features. These show ingresses and egresses that make it possible to draw the line as in example 1.

   # a snippet from elasticsearch manifest.yaml

    id: elasticsearch

    ingresses:

      - id: ingress-cluster-elasticsearch

        name: Internal ingress to elasticsearch

        isExternal: False

        default: True

   # a snippet from filebeatmqtt manifest.yaml

    id: filebeatmqtt

    connections:

      - id: egress-elasticsearch

        description: Egress to elasticsearch

        featureReference: elasticsearch

  • a very important fact is that the ingress feature is not aware of the name of the egress feature. On the other hand, the egress feature is aware of what feature it connects to. See featureReference property on filebeatmqtt manifest.yaml

What is an external route?

  • an external route is a connection from outside of the architecture into a feature in the architecture

  • there is only one feature participating in the route. That is the ingress feature.


Ingress definition deep dive:

  • id: string | unique ingress name, name must be same as patch name

  • name: string | human readable name of ingress to be displayed in UI

  • isExternal: boolean | defines if the ingress is meant to be used with connections from outside of the architecture

  • default: boolean | defines if the ingress should be chosen by the UI if there is more than 1 ingress with same isExternal flag

Egress definition deep dive

  • id: string | unique connection name, name must be same as patch name

  • name: string | human readable name of connection to be displayed in UI

  • featureReference: string | must be equal to the name of the ingress feature


Patching files

Every *egress|ingress uses a corresponding .json file with the same name as the name of the ingress|egress. For example, given this ingress in manifest.yaml:

   ingresses:

      - id: ingress-influxdb-docker

        name: Docker ingress to influxdb

        isExternal: False

        default: True

a file with name ingress-influxdb-docker.json must exist in routing directory, like so:

influxdb_docker

└── 2.5.1

    ├── container

    │   ├── docker

    │   │   └── docker-compose.yaml

    │   ├── library

    │   │   └── routingConfig.json

    │   ├── routing

    │   │   └── ingress-influxdb-docker.json <<--- here it is

    │   └── schemas

    │       ├── ui.schema.json

    │       └── ui.schema.midlayer.json

    └── manifest.yaml

Patch file specification

As stated above, every ingress | egress has a json patch file. The object in the file has three properties:

  • externalVariables

  • files

  • returns

Examples

managementstudio feature has an external ingress

     - id: ingress-external-managementstudio

        name: External ingress to managementstudio

        isExternal: True

        default: True

The patch file is:

{

  "externalVariables": [],

  "files": {

    "values.yaml": [

      {

        "title": "Enable external connection ingress to Management Studio",

        "comparison": [

          {

            "type": "always"

          }

        ],

        "operations": [

          {

            "op": "add",

            "path": "/self/grafana/ingress/enabled",

            "value": true

          }

        ]

      }

    ]

  }

}

This can be interpreted as:

  • do not use any external variables

  • on the values.yaml file perform one operation. The operation toggles grafana/ingress/enabled flag from false to true. The self references the values.yaml file.

  • return nothing

mosquitto-docker feature has an internal ingress

     - id: ingress-mosquitto-docker

        name: Ingress to mosquitto_docker

        isExternal: False

        default: True

The patch file is:

{

    "externalVariables": [],

    "files": {},

    "returns": {

        "servicePort": {

            "file": "routingConfig.json",

            "path": "/self/SERVICE_PORT"

        },

        "serviceProtocol": {

            "file": "routingConfig.json",

            "path": "/self/PROTOCOL"

        }

    }

}

This can be interpreted as:

  • do not use any externalVariables

  • return

{

  "servicePort": "property SERVICE_PORT from file routingConfig.json",

  "serviceProtocol": "property PROTOCOL from file routingConfig.json"

}

Why do we need return values?

An internal route has two participating features, ingress feature and egress feature. The ingress feature is the one that takes priority and is the “owner” of the connection. The ingress feature tells the egress feature what parameters it should use to connect to it. This is where the return values come in place. Let’s take a concrete example and make a route from modbus2mqtt to mosquitto-docker.

  1. User drags mosquitto-docker into a nodegroup.

  2. User changes the port on mosquitto-docker in the UI like so:


3. User drags `modbus2mqtt` and drags a line from it to `mosquitto-docker`

That brings us to the conclusion that only the mosquitto-docker feature is aware of the port number and it should propagate that to the egress feature, i.e modbus2mqtt. That is done with return values.

How is this example processed?

  1. mosquitto-docker's uiSchema.midlayer.json is processed first to save the port number the user has entered in the UI. It will patch two files: docker-compose.yaml and routingConfig.json

{

    "Service Port": {

        "docker-compose.yaml": [

            {

                "title": "Set servicePort",

                "comparison": [

                    {

                        "type": "always"

                    }

                ],

                "operations": [

                    {

                        "op": "textReplacement",

                        "path": "/self/services/mosquitto/ports/0",

                        "from": "/externalVariables/userInput",

                        "variableName": "SERVICE_PORT"

                    }

                ]

            }

        ],

        "routingConfig.json": [

            {

                "title": "Set servicePort",

                "comparison": [

                    {

                        "type": "always"

                    }

                ],

                "operations": [

                    {

                        "op": "copy",

                        "path": "/self/SERVICE_PORT",

                        "from": "/externalVariables/userInput"

                    }

                ]

            }

        ]

    }

}

  1. mosquitto-docker's ingress-mosquitto-docker.json is processed. It doesn’t edit any files. It reads routingConfig.json file and assigns servicePort property to externalVariables:

{

    "externalVariables": [],

    "files": {},

    "returns": {

        "servicePort": {

            "file": "routingConfig.json",

            "path": "/self/SERVICE_PORT"

        }

    }

}

  1. modbus2mqtt 's egress-mosquitto-docker.json is processed. It reads the servicePort from externalVariables and sets its value to commonconfig.json:

{

    "externalVariables": [

        "servicePort"

    ],

    "files": {

        "commonconfig.json": [

            {

                "title": "Route to filebeatmqtt",

                "comparison": [

                    {

                        "type": "always"

                    }

                ],

                "operations": [

                    {

                        "op": "copy",

                        "path": "/self/Mqtt/Port",

                        "from": "/externalVariables/servicePort"

                    }

                ]

            }

        ]

    }

}

The example above shows that ingresses are processed first and they return externalVariables that are fed into egress features.


Helmchart features and routing

It is impossible to predict routing properties for helmchart features without running helmchart template command. Let’s take this example:

  1. User changes attributes on elasticsearch in the UI.

  2. These changes are applied on values.yaml file with uiSchema.midlayer.json

  3. If one of the properties changed is Enable security then we are not able to predict ingress properties locally, and therefore helm template command must be run:

helm template --release-name "elasticsearch" --values="{path/to/values.yaml}" oci://europe-north1-docker.pkg.dev/pratexo-public-5b5a/helm-charts/elasticsearch

  1. That requires routing.yaml template to be present in elasticsearch helmchart.\

apiVersion: v1

kind: ConfigMap

metadata:

  name: {{ include "common.names.fullname" . }}-pratexo-routing

  labels: {{- include "common.labels.standard" . | nindent 4 }}

data:

  serviceName: {{ include "elasticsearch.coordinating.fullname" . }}

  servicePort: {{ .Values.coordinating.service.port | quote }}

  certificates: {{ include "elasticsearch.coordinating.fullname" . }}-crt

  secrets: {{ template "common.names.fullname" . }}

  security: {{ .Values.security.enabled | quote }}

  username: {{ .Values.security.elasticUsername | quote }}

  1. The data returned from the helm template command becomes an external variable named ingressConfig that is then fed into egress features as described in previous examples.

{

    "externalVariables": [

        "ingressConfig"

    ],

    "files": {

        "values.yaml": [

            {

                "title": "Set serviceName elasticsearch",

                "comparison": [

                    {

                        "type": "always"

                    }

                ],

                "operations": [

                    {

                        "op": "copy",

                        "path": "/self/elasticsearch/port",

                        "from": "/externalVariables/ingressConfig/servicePort"

                    }

                ]

            }

        ]

    }

}


Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article