Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Registration Details #22

Closed
emanuelpalm opened this issue May 12, 2021 · 17 comments
Closed

Service Registration Details #22

emanuelpalm opened this issue May 12, 2021 · 17 comments
Assignees
Labels
5.0 Core System: Service Registry The issue concerns the Core Service Registry system enhancement New feature or request

Comments

@emanuelpalm
Copy link
Contributor

emanuelpalm commented May 12, 2021

EDIT: See #22 (comment) for another proposal.

This proposal replaces #14.

When a service is registered, details are provided that let other systems consume that service. The scheme that is currently being used makes assumptions that work well for HTTP, but become less and less appropriate as the protocol used by the service deviates more from HTTP. It also needlessly associates systems with ports, which makes it less straightforward to register a service multiple times with different ports. Additionally, there are other minor improvements I perceive could be made, which I also present here.

Current Situation

Currently, a service registration message looks as follows:

{
  "serviceDefinition": "<service name>",
  "providerSystem": {
    "systemName": "<system name>",
    "address": "<hostname or IP address>",
    "port": "<0-65535>",
    "authenticationInfo": "<Base64-encoded X.509 SubjectPublicKeyInfo>"
  },
  "serviceUri": "<a URL base path>",
  "endOfValidity": "<yyyy-mm-dd hh:ii:ss>",
  "secure": "<one out of 'INSECURE', 'TOKEN' or 'CERTIFICATE'>",
  "metadata": { "<key>": "<value>", "..." },
  "version": "<integer>",
  "interfaces": [ "<protocol>-<security>-<encoding>", "..." ]
}

Proposal

{
  "name": "<service name>",
  "provider": {
    "name": "<system name>",
    "interfaces": {
      "dns": ["<DNS name 1>", "<DNS name 2>", "..."],
      "ip4": ["<IPv4 address 1>", "<IPv4 address 2>", "..."],
      "ip6": ["<IPv6 address 1>", "<IPv6 address 2>", "..."],
      "...": "..."
    },
    "identities": {
      "x509PublicKey": "<Base64-encoded X.509 SubjectPublicKeyInfo>",
      "...": "..."
    }
  },
  "expiresAt": "<integer being a unix timestamp in seconds>",
  "policies": {
    "security": "<one out of 'INSECURE', 'TOKEN' or 'CERTIFICATE'>",
    "...": "..."
  },
  "metadata": { "<key>": "<value>", "..." },
  "version": "<major>.<minor>.<patch>",
  "interfaces": [
    {
      "protocol": "<transport protocol, such as 'https', 'coaps+tcp', 'json-rpc' or 'mqtt'>",
      "<protocol-specific-fields>": "<depending on the protocol, additional fields could be appropriate, such as 'version', 'encodings', 'port' or 'basePath' or 'topic'>"
    }
  ]
}

The changes are as follows:

  1. "serviceDefinition" is renamed to "name". The service function through which the message is sent make its type unambigous. "name" is shorter.
  2. "providerSystem" is renamed to "provider". Only systems can be providers, the qualification is redundant.
  3. "providerSystem.systemName" is renamed to "provider.name".
  4. "providerSystem.address" is renamed to "provider.interfaces" and its value changes from a string to a map of array of strings. Valid map keys are initially "dns", "ip4"and "ip6", each of which is associated with an array of DNS names, IPv4 addresses or IPv6 address, respectively. The new format allows for the system to be reached via multiple methods, as well as clearly denoting what methods are supported. It cleanly supports more methods being added in the future, which is key to the claim I assume we make of Arrowhead being transport agnostic.
  5. "providerSystem.port" is moved into "interfaces[i].port" and is only required for interfaces with protocols that use ports. Not all services provided by the same system need to use the same port. Furthermore, the same service can be provided multiple times over different protocols, which may require that different ports be used. See Avoiding stale service registry entries  core-java-spring#192 for an earlier discussion.
  6. "providerSystem.authenticationInfo" is renamed to "provider.identities" and goes from being a simple string to being a map of strings. The map is initially only allowed to contain the single entry "x509PublicKey", but may be allowed to contain additional fields as support for more system identification methods are added to Arrowhead.
  7. "serviceUri" is renamed and moved to interfaces[i].basePath. Only interfaces with protocols that utilize paths are expected to specify the moved field.
  8. "endOfValidity" is renamed to "expiresAt" and changes format from something resembling ISO8601 to a plain Unix timestamp. The new name is shorter, and Unix timestamps require less space and are easier to parse than ISO8601 strings.
  9. "secure" is renamed to "policies" and its value changes from being a string to a map of strings. Initially, the only valid key is "security", but other kinds of policies could be added in the future. Examples of other policies could include quality-of-service, safety or maintenance. See Does secure field in ServiceRegistryRequestDTO and other places identify an access policy? core-java-spring#198 for a discussion about new values for the "security" field.
  10. "version" value is changed from a number to a string, expected to adhere to semver (see https://github.com/eclipse-arrowhead/core-java-spring/issues/194).
  11. "interfaces" is changed from an array of <protocol>-<secure>-<encoding> triplets to JSON objects that must specify a "protocol" field that names a complete transport protocol stack. Other fields are added depending on what protocol is specified. If the "protocol" is "http", "https" or "coap", a "contentTypes", "port" and a "basePath" field would also be provided. Note that "protocol" names the complete protocol stack, which means that "http" and "https" are not the same. It is valid for the same protocol to be listed more than once, as long as the value of at least one field is disjoint from every other entry (i.e. no entry is identical to or a subset of another entry).

Example

{
  "name": "contract-negotiation",
  "provider": {
    "name": "contract-proxy-1",
    "interfaces": {
      "dns": ["contract-proxy-1.local"],
      "ip4": ["192.168.14.98"]
    },
    "identities": {
      "x509PublicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp2w+8HUdECo8V5yuKYrWJmUbLtD6nSyVifN543axXvNSFzQfWNOGVkMsCo6W4hpl5eHv1p9Hqdcf/ZYQDWCK726u6hsZA81AblAOOXKaUaxvFC+ZKRJf+MtUGnv0v7CrGoblm1mMC/OQI1JfSsYi68EpnaOLepTZw+GLTnusQgwIDAQAB"
    }
  },
  "expiresAt": 1620818114,
  "policies": {
    "security": "TOKEN"
  },
  "version": "1.1.5",
  "interfaces": [
    {
      "protocol": "https",
      "contentTypes": ["application/json", "application/cbor"],
      "port": 443,
      "basePath": "contract-negotiation/"
    },
    {
      "protocol": "amqps",
      "contentTypes": ["application/json"],
      "port": 5671,
      "topic": "contract-negotiation.*"
    },
    {
      "protocol": "jsonrpc-tls",
      "jsonrpc": "2.0",
      "port": 8443
    },
    {
      "protocol": "jsonrpc-unix",
      "jsonrpc": "2.0",
      "path": "/tmp/contract-proxy-1/contract-negotiation.sock"
    }
  ]
}

EDIT 1: Remove "encodings" field and add "contentTypes" fields where relevant. Add JSONRPC to example.
EDIT 2: Restructure "provider.interfaces" and "provider.x509PublicKey".
EDIT 3: The field accessPolicy is renamed to consumptionPolicy.
EDIT 4: The field consumptionPolicy is renamed to policies and made into a map. This leaves room for other kinds of policies in the future.

@emanuelpalm
Copy link
Contributor Author

@tsvetlin @jerkerdelsing Did our previous discussions on this topic lead anywhere? Could this land in Arrowhead 5.0?

@emanuelpalm
Copy link
Contributor Author

An interesting thing to note with this new Service registration record is that all the data in the provider field also must be stated in the X.509 certificate of that provider. The provider.name field is the subject name CN, the provider.interfaces correspond to the subject alternative names extension, and, finally, the provider.x509PublicKey is taken from the subject public key information field.

@jenseliasson
Copy link

In general, I like your proposal. Having an array for addresses is a big step forward as often a service is available over different addresses.

What about MQTT? Do you want the topic to be encoded in the basePath?

We are seeing a lot mir use for different protocols (MQTT, CoAP, Websockets, etc) so it really makes sense to minimise any hard http limitations.

I think this proposal should be discussed more deeply.

@emanuelpalm
Copy link
Contributor Author

@jenseliasson We would have to decide on an "interface" structure for each relevant protocol. You have much more hands-on experience than I do with MQTT, so you are in a better position to propose a useful set of fields. If you look on the example structure, you can see that the AMQPS protocol has a topic rather than a base path.

@jerkerdelsing
Copy link
Member

This is to some part related to the meta data discussion in the roadmapping group. If we can structure the meta data into a number of areas with little of no dependencies like:

  • Protocols
  • Datamodels
  • Deployment
  • ....

Then the theses areas can be addressed and agreed upon separately.

@jerkerdelsing jerkerdelsing added 4.5 Intended for release 4.5.0 5.0 and removed 4.5 Intended for release 4.5.0 labels May 15, 2021
@emanuelpalm
Copy link
Contributor Author

@jerkerdelsing Who coordinates the metadata discussion? Could someone be tasked with summarizing the current state in an issue and then @mention everyone that should have interest in that discussion? I'd very much prefer for that discussion to primarily be carried our here. This kind of matter typically requires a bit of concentration to come up with new ideas. In my experience, live meetings are great for presenting things you have already thought about, but not so much for coming up with new ideas.

@emanuelpalm
Copy link
Contributor Author

@jenseliasson @jerkerdelsing This format does have bearing on how data is structured by the Service Registry, which means that this issue is related to #21, for example.

@emanuelpalm
Copy link
Contributor Author

emanuelpalm commented May 17, 2021

Here is a proposal for an initial set of "interfaces" protocol structures:

HTTP(S)

{
  "protocol": "<'http' or 'https'>",
  "contentTypes": ["<Content-Type>", "..."],
  "versions": ["1.0", "1.1", "2.0", "..."],
  "port": "<0-65535>",
  "basePath": "<base-path>"
}
  • "protocol" is "http" for non-secure communications and "https" for secure communications (TLS).
  • "contentTypes" is an array containing all Content-Type header values a response from the service can contain, as defined by RFC7231, Section 3.1.1.5. It also specifies what media types may be used in requests sent to the service (for applicable methods, such as PUT and POST). Note that wildcards, or any other constructs unique to the Accept field, are not allowed (e.g. */html or */*). See also RFC6838 and IANA Media Types. If exactly one content type is specified, no indication is required in either requests or responses regarding what format to use. If more than one content format is specified in the array, the following behavior must be the case:
    1. if a request specifies one of the array content types in its Accept header, the response must use that content type,
    2. if a request specifies a listed content type its Content-Type header (due to containing a payload) but lacks an Accept header, the response must use the specified Content-Type,
    3. if a request does not specify any Content-Type or Accept header, the first content format in the array must be assumed for the request payload (if any) and used in the response, or
    4. in any other case the service must respond with 406 Not Acceptable.
  • "versions"is optional and defaults to ["1.1"].
  • "port" is optional and defaults to 80 or 443 depending on whether "protocol" is "http" or "https", respectively.
  • "basePath" is optional and defaults to "/". A base path should always be specified.

CoAP(S)

{
  "protocol": "<'coap', 'coaps', 'coap+tcp', 'coaps+tcp', 'coap+ws' or 'coaps+ws'>",
  "contentFormats": ["<Content-Format ID>", "..."],
  "versions": ["1"],
  "port": "<0-65535>",
  "basePath": "<base-path>"
}
  • "protocol" is one of the URI scheme identifiers specified in either RFC7252, Section 6 or RFC8328, Section 8, which means that CoAP can be served via plain UDP, DTLS, plain TCP, TLS, plain WebSockets or TLS over WebSockets.
  • "contentFormats" is an array containing a set of the integer identifiers specified in Constrained RESTful Environments (CoRE) Parameters, CoAP Content Formats. They are used to specify what media types may be used in request payloads and may be received in response payloads. If exactly one content format is specified, no indication is required in either requests or responses regarding what format to use. If more than one content format is specified in the array, the following behavior, which harmonizes with that described in RFC7252, Section 5.10.4, must be the case:
    1. if a request specifies one of the array content formats in its Accept option, the response must use that content format,
    2. if a request specifies a listed content format its Content-Format option (due to containing a payload) but lacks an Accept option, the response must use the specified Content-Format,
    3. if a request does not specify any Content-Format or Accept option, the first content format in the array must be assumed for the request payload (if any) and used in the response, or
    4. in any other case the service must respond with 4.06 "Not Acceptable".
  • "versions" is optional and defaults to ["1"] (the only currently existing version).
  • "port" is optional and defaults to 5683 if "protocol" is "coap" or "coap+tcp", to 5684 if "protocol" is "coaps" or "coaps+tcp", to 80 if "protocol" is "coap+ws", and to 443 if "protocol" is "coaps+ws".
  • "basePath" is a prefix to be added to any "Uri-Path" included in any requests to the service. Defaults to "/" (the empty path), which is treated as no prefix being used. A base path should always be specified.

@emanuelpalm
Copy link
Contributor Author

@jerkerdelsing Any news on how this relates to the meta data discussion you mentioned earlier?

@emanuelpalm emanuelpalm added Core System: Service Registry The issue concerns the Core Service Registry system enhancement New feature or request labels Oct 28, 2021
@emanuelpalm emanuelpalm self-assigned this Oct 28, 2021
@emanuelpalm
Copy link
Contributor Author

@jerkerdelsing Will this really make it into the 4.5 release? It is a rather big change to how the SR works. I would rather target 5.0. We should also discuss about reviewing and ratifying this proposal in an upcoming Roadmap WG meeting.

@vanDeventer
Copy link

I am not crazy about the word "interface" since it can be a reference type in some computer language and is not clear (at least to me). I would prefer "networkInterface" or "internetInterface" at the Internet layer, and applicationInterface at the application layer.

When it comes to the metadata discussion, I would like to see some metadata hardcoded (e.g., manufacturer or developer) and some part of the properties file so that they can be updated at deployment (e.g., location).

@emanuelpalm
Copy link
Contributor Author

@vanDeventer Well, an interface is where two communicating entities meet, as I'm very sure you already know. Interface means "between faces". If I'm not mistaken, the term ended up in computer languages because of object oriented programming, which originally was explained as a system of "communicating objects". The message analogy was everywhere, and the term "interface" was taken from the network equipment field (for example, Objective-C has something reminiscent of Java interfaces called "protocols", which, obviously, is also taken from the same source). Service-oriented architecture is also very much concerned with the message analogy (perhaps not even an analogy, as the messages are meant to be sent between machines). I think the interface term is apt enough.

In the new version of the reference model, which I'm currently trying to finish at the moment (I'm officially on leave but try to use an hour or two every now and again), has four interface levels: (1) network, (2) system, (3), service and (4) operation. I'm against using either of the TCP/IP and OSI models because they don't map very well to the device, system, service and operation constructs of Arrowhead. Also, as I've mentioned in our e-mials, there are no particular details in any communication protocol that maps neatly into layers. Which of the five TCP/IP model layers would you name the "interface" field after? Whichever name you choose, information sometimes associated with the other layers would have to also go into that "interface" field.

@emanuelpalm
Copy link
Contributor Author

emanuelpalm commented Dec 13, 2021

Now when we are discussing this here, I might just as well show the new proposal for structure of the service registry details.

{
  "id": "<service instance identifier>",
  "type": {
    "id": "<service type identifier>",
    "version": "1.2"
  },
  "expiresAt": "<integer being a unix timestamp in seconds; denoting when the service registry entry expires>",
  "metadata": { "<key>": "<value>", "..." },
  "interfaces": [
    {
      "protocol": {
        "name": "<transport protocol, such as 'https', 'coaps+tcp', 'json-rpc' or 'mqtt'>",
        "<protocol-specific-fields>": "<depending on the protocol, additional fields could be appropriate, such as 'version', 'contentTypes', 'ipv4Address', 'port', 'x509PublicKey', 'basePath' or 'topic'>"
      },
      "policies": [
        {
          "name": "<policy name, such as 'tokenAuthentication'>",
          "<policy-specific-fields>": "<depending on the policy, additional fields could be appropriate, such as 'scopes', 'version', 'tokenType', and so on>"
        }
      ]
    }
  ]
}

​The big difference is that all interface and policy information is moved into the elements of the "interfaces" array. By the way, policies are concerned with what is permitted, while protocols are concerned with what messages mean and where they are going. Another big change is that the provider of the service is no longer identified. As far as I can tell, that information is not strictly necessary. If it would be required for certain use cases, we could add an optional field or decide on a "metadata" field name for it.

By the way, I'm not very comfortable with the "metadata" field. I believe we need to have a rigid set of criteria for what kind of information is allowed to go in there, or we may end up with issues down the road because the field is abused beyond reason. If we can't find a suitable set of such a criteria, I propose removing the field entirely.

EDIT: Make "type" into an object and move "version" into it. It makes it less ambiguous what is being subject to the version number.

@vanDeventer
Copy link

@emanuelpalm I am OK with your use of the word interface. Do you have populated examples of what that would look like for different systems/services?

I have a strange question, which is related to my office situation. Sometimes I get eduroam and sometimes I do not. My question then is: when a system/device uses WiFi and that interface is initially there and then disappear, which software is responsible to update the service registry? How is that done?

@jenseliasson
Copy link

Regarding your "strange question": that is not strange at all. The way we do it on our IoT gateways is that the Arrowhead system that handles everything is constantly checking connectivity and updates the Service registry when things change.

One problem that we have seen is that the link between a system and its IP address and port is very static. The way I see it, an IP address (and port for that matter) can easily change during a system's lifetime. If we look at the Orchestrator for example, when adding a rule, the IP and port of the provider is part of the request:
...
"providerSystem": {
"systemName": "string",
"address": "string",
"port": 0
},
...

So if a producer changes IP address, the orchestration rule is no longer valid I guess. Plus, all address fields should be an array of addresses since it is not uncommon for a device to have multiple network interfaces (Wi-Fi, Ethernet, ...)
In my mind, the ONLY identifier that should be used is systemName. IP and port should be looked up dynamically in the Service registry. The use of the port is also a quite blunt tool since not all protocols are based on ports (like MQTT). Multiple AH systems can also share the same IP address, so again the IP is not a good identifier for a unique system.

So, to summarize: address and port should not be used to identify a system, and a providing system is responsible for updating the SR when things change.

This became a bit of a rant, but these are my comments.

@emanuelpalm
Copy link
Contributor Author

emanuelpalm commented Dec 28, 2021

@emanuelpalm I am OK with your use of the word interface. Do you have populated examples of what that would look like for different systems/services?

Not really, but I could make one up now. As such:

{
  "id": "mOwy9rSK4FqSv9GSXmGxQ",
  "type": {
    "id": "my-special-service-type",
    "version": "1.2"
  },
  "expiresAt": 1641700000,
  "interfaces": [
    {
      "protocol": {
        "name": "https",
        "contentTypes": ["application/json", "application/cbor"],
        "versions": ["1.0", "1.1"],
        "basePath": "gzqetd6w/",
        "addresses": [
          {"type": "ipv4", "name": "192.168.14.23", "port": 9943},
          {"type": "dns", "name": "service-dns-name", "port": 9943}
        ]
      },
      "policies": [
        {
          "name": "http-bearer-authentication",
          "authenticatorIds": [
            "vIeO3XnNt8mlPIgeiqtg6Ldn4Z"
          ]
        }
      ]
    }
  ]
}

Note that that "http-bearer-authenication" policy is just something pulled out of the air. It probably needs to be revised before being fully practical. The "authenticatorIds" field is meant to identify the authorization services that dispense the bearer tokens required to satisfy the policy.

EDIT: Updated this to reflect the changes in #22 (comment) and made the authentication policy less complex.

EDIT 2: There is no need to have X.509 fingerprints in the interfaces[].protocol object. The system approaching the service must already trust the service registry, which means that it trusts the service registry's cloud certificate, which is the same certificate that must be trusted to contact the described service. Also added missing braces around the policy object.

I have a strange question, which is related to my office situation. Sometimes I get eduroam and sometimes I do not. My question then is: when a system/device uses WiFi and that interface is initially there and then disappear, which software is responsible to update the service registry? How is that done?

My biased and partial understanding and desire is that the service registry should function as follows:

                         +----------------------------+
                         | "Plant Description System" |
                         +----------------------------+
                                       |
                                       A
                                       O
                                       |
+----------------------+        +--------------+        +---------------------+
| Authorization System |-o>--+--| Orchestrator |-----<o-| "Monitoring System" |
+----------------------+     |  +--------------+        +---------------------+
                             |         |                          |
+----------------------+     |         O                          |
|   Service Registry   |-o>--+         V                          |
+----------------------+               |                          |
           |                    +--------------+                  |
           O>-------------------|   "System"   |-o>---------------+
                                +--------------+

In other words, the orchestrator receives instructions about how to orchestrate from some arbitrary "Plant Description System". Subsequently, it collects information from an Authorization System, a Service Registry and a "Monitoring System" yet to be specified. The orchestrator adjusts the rules it presents in relation to information about systems not being available, for example.

I don't think the Service Registry should be concerned at all with whether or not the services it lists are online or not. When a system wishes to consume a service, it should always first contact the orchestrator, which will then name the services (by ID) that should be consumed. The Service Registry is only contacted to get the details on these services.

To be able to better handle intermittent connections and load balancing, etc, the orchestrator response could include priority lists, for example.

@tsvetlin tsvetlin removed the 4.5 Intended for release 4.5.0 label Mar 1, 2022
@jerkerdelsing
Copy link
Member

See MoM of issues #44

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
5.0 Core System: Service Registry The issue concerns the Core Service Registry system enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants