eclipse-arrowhead / roadmap

Eclipse Public License 2.0
5 stars 9 forks source link

Service Registration Details #22

Closed emanuelpalm closed 2 years ago

emanuelpalm commented 3 years ago

EDIT: See https://github.com/eclipse-arrowhead/roadmap/issues/22#issuecomment-992862174 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 https://github.com/eclipse-arrowhead/core-java-spring/issues/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 https://github.com/eclipse-arrowhead/core-java-spring/issues/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 commented 3 years ago

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

emanuelpalm commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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:

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

emanuelpalm commented 3 years ago

@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 commented 3 years ago

@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 commented 3 years ago

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>"
}

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>"
}
emanuelpalm commented 3 years ago

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

emanuelpalm commented 3 years ago

@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 commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

@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 https://github.com/eclipse-arrowhead/roadmap/issues/22#issuecomment-992862174 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.

jerkerdelsing commented 2 years ago

See MoM of issues #44