-
Notifications
You must be signed in to change notification settings - Fork 63
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
How do you cancel or query the state of an action request? #302
Comments
To provide some additional context for people who don't want to read the Web Thing API specification... To request an action, the Web Thing API uses a POST request, e.g.
Response:
You can get a list of current action requests. Request:
Response:
You can get the status of an action request. Request:
Response:
You can cancel an action request. Request: Response: You can also get a list of action requests of all types with a GET request to an Actions resource (whose URL is provided by the top level links member). Request:
Response:
And for completeness you can also request an action on the top level Actions resource if you want to. Request:
Response:
How can all of this be expressed in a Thing Description, including the payload formats and possible error responses? |
By contrast, in Arena, HTTPS POST takes the action input as the body of the request, and returns the action output as the body of the response. This follows the basic semantics for POST. If a developer wants a cancellable process that is initiated by an action, that can be layered on top of the core patterns of actions and events. You could return a process ID for the action that initiates the process, and provide another action to cancel an active process using the process ID. You can likewise define a progress event that passes the process ID together with status information. My take is thus that the core semantics for the Web of Things should be really simple and more complex models can be layered on top. This principle also applies to APIs for event logs and for querying a history of property updates in the case of telemetry streams. |
If the argument for forms vs. links is for backwards compatibility with existing IoT APIs using declarative protocol bindings, then it should be possible to describe the API described above using a Thing Description alone. Describing Mozilla's Web Thing API should be a particularly easy example as it was developed in parallel with the Thing Description specification and its data model is already aligned. The solution you describe requires changing the API itself, and doesn't explain how the client would know from the Thing Description that the ID returned by the action request can be used to cancel that request by using a different action. Can you provide an example Thing Description which provides the client with all of that information in a declarative protocol binding? |
What happens if the action is a long running process which doesn't complete by the time the HTTP request times out? (This was the reason we created the action queue API, and is a difference between requesting an action and and simply setting a property) |
Developers are responsible for documenting the purpose of properties, actions and events. Picking appropriate names would help, e.g. an action called cancelProcess with an input named processID. |
The timeout for HTTP requests is often client dependent. We could standardise how to express an indication of the maximum expected duration of a long lived process as part of the metadata for an action. Alternatively, developers could use the processID design pattern described above. |
This assumes the involvement of a human to interpret those names and write custom code for that specific web thing, which doesn't allow for ad-hoc interoperability.
The duration of an action may be user defined e.g. an action to fade a light from 0 to 100% brightness over the course of 1 hour. Again, your proposed solution requires changing the API. But how would you get the current status of an action in this model? e.g. to find out if the action succeeded or failed. |
I agree that when it comes to describing existing services using a standardised machine interpretable format, this gets increasingly complicated, and I question whether this complexity is justified. In the longer time frame we can and should encourage convergence in protocols and their usage across the Internet and this would render declarative protocol bindings a historical legacy that is no longer needed. An alternative is to provide a platform ID that web of things clients can used to identify what protocols and usage patterns apply to a given thing. This avoids the need for complex representations in every TD. |
The point I'm making here is that it's not just difficult to be backwards compatible with existing APIs, it may actually be impossible without significantly more complexity than is currently allowed for in the current specification. Is anyone willing to make a stab at describing Mozilla's Web Thing REST API in a Thing Description alone? The issue described here is just one of several problems with trying to do that. (I think we've already agreed that the Web Thing WebSocket API can not be described in a Thing Description and would require a separate WebSocket subprotocol specification.) |
Yes, but the developer should have some understanding of what the maximum is likely to be before the process can be considered to have failed. That expectation could be given as a metadata property.
Yes, but see my previous post that questions the long term commercial need for a complex declarative protocol binding standard.
You would listen to the status events. In addition, I would design the server and the application to be robust against a loss of network connectivity, the reboot of the client or server, etc. |
We have been discussing this for a long time -- since IG-only work -- and also came to good conclusion about this. The ideal way to this is using hypermedia where a running Action is represented as a Web resource that is dynamically created upon invokaction. This running Action itself can have Properties and Actions itself again. Maybe you remember the discussions we had around a potential We do have all required extension points in place for this: The output of an Action can be such an To date, support for this is very limited in existing systems; hypermedia concepts are almost nil. Thus, we decided to focus on description of deployed systems for now and tackle this issue in the next charter period or in the IG first. The closest we have are custom "ticket responses" that each platform does in a different style. This must be solved by semantically describing the response content and leave it to the application. @draggett described this approach in his comment further up. |
My very old proposal was to support things as first class types. However, I agree that this something we can leave to future extensions given the subtleties involved. |
Simply set the content type to |
That works for limited cases, but isn't a general solution. Object oriented programming languages support objects as first class types, so having things as first class types is something that will be expected for the web of things. At the protocol level we can pass things using the URI for their thing description, or as you suggest, by passing the JSON-LD for the thing description, both are a form of reference to a thing. Interestingly, by passing the JSON-LD explicitly, this corresponds to giving the thing a blank node for its RDF identifier. If the TD for a thing includes declarations of initial values, the platform should carry out the initialisation. If this involves a thing, the platform needs to retrieve the thing's TD, if not supplied in place, and initialise that thing. This can get a little complicated when you need to deal with forward references, and when the dependencies between things form cycles. I showed how to handle that over two years ago, proving that it is a tractable problem, just as it is for object oriented programming languages. |
Here is a proposal: in a future version of the TD model, we could at least standardize new operation types to cancel, query (and update?) an invoked action: something like |
Having said that, it is possible already with the current TD spec to specify operations on dynamically created resources. For that, you can define a generic action {
"@context": [
"https://www.w3.org/2019/wot/td/v1",
{
"ActionRequest": "http://example.org/ActionRequest",
"cancelaction": "http://example.org/cancelActionOperationType",
"queryaction": "http://example.org/queryActionOperationType"
}
],
"id": "urn:example:mylamp",
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": { "type": "number" },
"duration": { "type": "duration" }
}
},
"output": {
"type": "object",
"properties": {
"href": {
"@type": "ActionRequest",
"type": "string"
},
"status": {
"enum": [ "pending", "completed" ]
}
}
},
"forms": [
{
"href": "https://mythingserver.com/things/lamp/actions/fade",
"op": "invokeaction"
}
]
},
"manage": {
"uriVariables": {
"actionRequest": {
"@type": "ActionRequest",
"type": "string"
}
},
"forms": [
{
"href": "{actionRequest}",
"htv:methodName": "DELETE",
"op": "cancelaction"
},
{
"href": "{actionRequest}",
"htv:methodName": "GET",
"op": "queryaction"
}
]
}
}
} In this example, you can see that I use |
However, I would also expect (or wish) that future WoT Things have a more hypermedia-driven interface to consumers. In that case, the As @mkovatsc and @draggett said, the same structure as in the TD model could be used for the JSON response. I would suggest one conceptual variant, though: to me, reserving the class |
I support the idea at the previous comment by @vcharpenay. |
As discussed in the TD call on 7.2. we are looking at different examples. Oracle's IoT Cloud service has a hypermedia-based action model that supports synchronous and asynchronous operations. The response payload contains a key "complete", when the operation is already finished, otherwise the url endpoint contains a link to asynchronously query the status.
Here's the full API decumentation for Invoke action: Starting point for the API documentation: |
I would like to point out that Thing-Consumer protocol should always look forward, but not backward. This means, it had not better depend on transaction model where you can "cancel" a request while it is in action. Consumer should be able to make an independent "cancel" request to a Thing, and the Thing makes a best effort to fulfill the request. The fulfillment may be just stop the action, or Thing may wait the action to finish (if it cannot be stopped immediately) and revert to the original state if possible. Here, note that a Thing may be able to process the cancel request even after the original request was complete. This is why I said Thing-Consumer protocol should always look forward. Things can decide how best to process the "cancel" request because it just one of the subsequent action requests. |
I like the proposal. There's one aspect to consider: |
For an async operation the cancellation should also be async.
|
Does it make sense to introduce a hypermedia-specific navigation term that gives an indication of where the resource is defined in the payload message that can be used to query the status or cancel an action? E.g., in the case of Oracle it would be
for Mozilla it would look like
Btw: I have checked the MDSP API, and there seems to be no use of the hypermedia approach yet. |
On the Thing Description call today we discussed proposed In that issue I noted that there are three potential use cases for "querying" an action:
Do we need two operations for Action affordances which distinguish between the first two? E.g. |
I think, this is a similar analogy to readproperty and readallproperties. In this context it would make sense to have two. Maybe we should use the term queryallactions instead. Option 2 and 3 can be supported by a Thing implementation and be announced at the top level forms: "forms": [
{
"op": ["queryallactions"],
"href": "./actions/{ACTION_NAME}"
},
{
"op": ["queryallactions"],
"href": "./actions"
}
] |
@sebastiankb wrote:
I agree in that no. 2 is like
I agree the same name makes sense for both operations, but it may be tricky to define how a Consumer distinguishes between the two if they share the same name. I wish there was a word in the English language for an instance of an action, but I can't think of one. Some other ideas...
|
Yes, thats makes sense.One idea is to design the top-level form to inform the client that it can query actions with a filter by specifying the name of the actions in the URL, which will return only the status of all active actions with the corresponding action name.
If we introduce the convention then the client can distinguish based on the URL, right?
I would prefer no. II. |
regarding @benfrancis comment I will put this to the agenda of today's TD call. |
Just one argument regarding having An opposing argument based on the same "worry" I have: We should make sure that op keywords are different enough that a newcomer does not confuse actions with properties. Yet another comment: |
Note that the example Thing Description in #302 (comment) is now out of date. Following a review of the proposed action protocol binding for the Core Profile, both the synchronous and asynchronous responses follow the same data schema, which has been expanded to include a {
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:ex:thing",
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"duration": {
"type": "integer",
"minimum": 0,
"unit": "milliseconds"
}
}
},
"output": {},
"schemaDefinitions": {
"actionStatus": {
"status": {
"type": "string",
"enum": [ "pending", "running", "completed", "failed" ],
"required": true
},
"output": {
"required": false
},
"error": {
"type": "object",
"required": false
},
"href": {
"type": "string",
"const": "/fade/{id}",
"required": false
}
}
},
"forms": [
{
"href": "/fade",
"op": "invokeaction",
"htv:methodName": "POST",
"contentType":"application/json",
"response": {
"contentType": "application/json",
"schema": "actionStatus"
},
"additionalResponses": {
"success": "yes",
"contentType": "application/json",
"schema": "actionStatus",
"htv:headers": [
{
"htv:fieldName": "Location",
"htv:fieldValue": "/fade/{id}"
}
]
}
},
{
"href": "/fade/{id}",
"op": "queryaction",
"contentType":"application/json",
"htv:methodName": "GET",
"response": {
"contentType":"application/json",
"schema": "actionStatus"
}
},
{
"href": "/fade/{id}",
"op": "cancelaction",
"htv:methodName": "DELETE"
}
],
"uriVariables": {
"id": {
"type": "string",
"description": "identifier of action request"
}
}
}
} The notes from above still apply:
In addition to these notes:
Overall my impression is that it would be very difficult for a Consumer which didn't explicitly implement the Core Profile Protocol Binding to interpret this Thing Description, but this is the closest I can get to providing a declarative equivalent of the concrete protocol binding described in the specification. Note that my intention is that a Web Thing using the Core Profile would expose a much simpler Thing Description than this, this is just a canonical(ish) example of what it might look like once all the defaults defined in the Core Profile Protocol Binding have been applied, and how the full protocol binding would have to be described for a Consumer which doesn't implement the Core Profile. I think the important action item here is to decide whether to add the |
E.g. {
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:ex:thing",
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"duration": {
"type": "integer",
"minimum": 0,
"unit": "milliseconds"
}
}
},
"output": {},
"forms": [
{
"href": "/fade",
"op": "invokeaction"
},
{
"href": "/fade/{id}",
"op": "queryaction"
},
{
"href": "/fade/{id}",
"op": "cancelaction"
}
],
"uriVariables": {
"id": {
"type": "string",
"description": "identifier of action request"
}
}
}
} |
@egekorkan wrote:
If an operation using an HTTP request like I think the key difference between properties and actions is that a property only has one value at any one time, whereas an action may have multiple running instances (in serial or in parallel). So whilst a property is likely to be bound to a single resource (hence the singular terms I think we basically need to decide whether the term "action" in operation names refers to: A) the collection, e.g.
B) an individual instance of the interaction, e.g.
Which works best? |
Not sure if I should comment here or at #1208 but I think that there are some problems when one thinks of the Consumer applications in cases that An important thing to highlight here is that for many devices there would be no real need to have dynamic ids if we do not want to queue multiple actions. If I am fading a lamp, rotating a robot, sprinkling water on a farm, my Thing can reject subsequent invoke actions if one is already being processed. Dynamic hrefs is more difficult to implement in a Thing and in Consumers so I would not want to promote their use in the TD specification. They should be of course possible to describe and they are needed for the WebThings API as well. Ideally, we should use static hrefs in most examples and then a separate section about how to managed dynamic hrefs in TDs. |
This is a reply to the example above.
Here is the version which would cover this: {
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:ex:thing",
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"duration": {
"type": "integer",
"minimum": 0,
"unit": "milliseconds"
}
}
},
"output": {},
"forms": [
{
"href": "/fade",
"op": "invokeaction",
"htv:methodName": "POST",
"contentType": "application/json",
"response": {
"htv:headers": [
{
"htv:fieldName": "Location",
"htv:fieldValue": "/fade/{id}"
}
]
}
},
{
"href": "/fade/{id}",
"op": "queryaction",
"htv:methodName": "GET",
"response": {
"contentType": "application/json",
"schema": "actionStatus"
}
},
{
"href": "/fade/{id}",
"op": "cancelaction",
"htv:methodName": "DELETE",
"contentType": "application/json"
}
],
"uriVariables": {
"id": {
"type": "string",
"description": "identifier of action request"
}
}
}
},
"schemaDefinitions": {
"actionStatus": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
]
},
"error": {
"type": "object"
}
},
"required": [
"status"
]
}
}
} I think, this looks ok for the dynamic URL approach with As @egekorkan also noted above, the question is whether his dynamic approach will always be associated with a physically based action such as "move robot arm" where there will be only one physically instance shared by different potential consumers. Perhaps there should also be a static approach that simplifies such handling and makes it clear that the consumer can directly query a single resource to check the action status of a single physical instance. Here would be an example based on the hypermedia control example: {
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:ex:thing",
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"duration": {
"type": "integer",
"minimum": 0,
"unit": "milliseconds"
}
}
},
"output": {},
"forms": [
{
"href": "/fade",
"op": "invokeaction",
"htv:methodName": "POST",
"contentType": "application/json"
},
{
"href": "/fade/status",
"op": "queryaction",
"htv:methodName": "GET",
"response": {
"contentType": "application/json",
"schema": "actionStatus"
}
},
{
"href": "/fade/cancel",
"op": "cancelaction",
"htv:methodName": "DELETE",
"contentType": "application/json"
}
]
}
},
"schemaDefinitions": {
"actionStatus": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
]
},
"error": {
"type": "object"
}
},
"required": [
"status"
]
}
}
} |
@sebastiankb Thank you for the clarifications. Your modified @egekorkan Note that currently #1208 doesn't say anything about dynamic resources, it just introduces the terms Perhaps we can land #1208 with that simple use case in mind, before dealing with more complex cases? I just wanted to highlight that there could be naming issues with meta-interactions further down the line. With regards to more complex use cases:
I agree with the observation in the README @egekorkan linked to that "everything gets quite complicated" when dealing with dynamic resources, and with @sebastiankb's point that it's not currently obvious how a Consumer is meant to infer relationships between dynamic URLs used across multiple Forms a Thing Description. In actual fact the original reason for me filing this issue in 2018 was to demonstrate the limitations of the Forms approach in Thing Descriptions, and argue for an alternative approach. I think it says a lot that three years down the line this still isn't possible. I can see two paths forward for dealing with these more complex use cases:
It could be possible for us to try both:
An alternative is that we give up on action queues and have the Core Profile only support a single instance of an action at a time, but that would mean there's still no way to describe the set of features already provided by WebThings and we'd have to drop features in order to be W3C compliant. |
TL;DRI also agree that we need to put down something first and maybe revise and refine (or even remove it) down the road. The simple use case seems well described with the current proposal and I would support moving forward. Having said that to have a complete solution we must solve also the dynamic problem. This would not be only useful for modeling actions queues, but I think it will help us scratch some itches for the TDD Thing Model. I don't know if I am the only one, but I see a strong similarity between TD description collections and Actions queues. In fact, we had a hard time figuring out how to model the CRUD operations on the TDD Thing Description collection. Ben knows that well. So how can we deal with collections? @benfrancis above proposed two valid points, but I want to add another one into the discussion. It is actually loosely based on @vcharpenay's approach's for hypermedia control and on some ideas that were already popping out during our calls. To put it simply:
Of course, this point deserves a better explanation (providing also a list of pros and cons) and if accepted would require some modifications in @benfrancis's core profile protocol. However, I don't clutter this issue cause I really want to see some progress here. My proposal is to review and merge #1208 with just the simplest use case in mind. |
Regarding dynamic TDs, this introduces a whole new can of worms for me. Then, other protocols can start describing ways to change TDs to implement some functionality. This is somewhat my personal opinion but I think that TDs should be always static in order to make it possible to proxy/bridge devices, generate user interfaces etc. Having dynamic TDs also introduces the problem of notifying the Consumer when that happens, more problematic if the Thing cannot host its own TD. |
For the record, the option that I am proposing diverges from @vcharpenay's on this point. I agree that dynamically changing TDs make everything even more tangled. In the short proposal with dynamically created TDs I stress the dynamic of the creation: TD stays static once retrieved. It basically is a discovery method -> getting a TD from an affordance invocation. |
Reflecting on this some more, I think of these two option A makes the most sense because it's consistent with properties and events. Subscribing to an event subscribes you to all instances of an event in the same way that querying an action queries all instances of an action ( I therefore propose the following operation names:
To demonstrate the difference between
{
"status": "pending"
}
[
{
"status": "completed",
"href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655"
},
{
"status": "pending",
"href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-431553"
}
]
{
"fade": [
{
"status": "completed",
"href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655"
},
{
"status": "pending",
"href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-431553"
},
{
"status": "pending",
"href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-51ff16"
}
],
"reboot": [
{
"status": "pending",
"href": "/things/lamp/actions/reboot/123e4567-e89b-12d3-a456-f3dea"
}
]
} |
from today's TD call:
|
Below is an example Thing Description of a Thing with a single Action called "fade", which demonstrates usage of each of these operations with an API which expands on the proposed action protocol binding for the Core Profile. See notes below. {
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:ex:thing",
"title": "Web Lamp",
"securityDefinitions": {
"basic_sc": {
"scheme": "basic",
"in": "header"
}
},
"actions": {
"fade": {
"input": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"duration": {
"type": "integer",
"minimum": 0,
"unit": "milliseconds"
}
}
},
"output": {},
"forms": [
{
"href": "/actions/fade",
"op": "invokeaction",
"htv:methodName": "POST",
"contentType": "application/json",
"response": {
"htv:headers": [
{
"htv:fieldName": "Location",
"htv:fieldValue": "/fade/{id}"
}
],
"contentType": "application/json",
"schema": "actionStatus"
}
},
{
"href": "/actions/fade",
"op": "queryaction",
"htv:methodName": "GET",
"response": {
"contentType": "application/json",
"schema": "actionStatusList"
}
},
{
"href": "/actions/fade/{id}",
"op": "queryactioninstance",
"htv:methodName": "GET",
"response": {
"contentType": "application/json",
"schema": "actionStatus"
}
},
{
"href": "/actions/fade/{id}",
"op": "cancelactioninstance",
"htv:methodName": "DELETE",
"contentType": "application/json"
}
],
"uriVariables": {
"id": {
"type": "string",
"description": "identifier of action request"
}
}
}
},
"forms": [
{
"href": "/actions",
"op": "queryallactions",
"htv:methodName": "GET"
}
],
"schemaDefinitions": {
"actionStatus": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
]
},
"output": {},
"error": {
"type": "object"
},
"href": {
"type": "string",
"format": "uri",
"const": "/actions/fade/{id}"
}
},
"required": [
"status"
]
},
"actionStatusList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
]
},
"output": {},
"error": {
"type": "object"
},
"href": {
"type": "string",
"format": "uri",
"const": "/actions/fade/{id}"
}
},
"required": [
"status"
]
}
}
}
} Open questions:
I can go ahead and modify #1208 to rename |
I have submitted an alternative PR (#1226) which uses the operation names If we can't come up with answers to those open questions then I think we may have to consider separating this issue into two separate issues to solve separately:
We could start by defining I think we're bumping up against two limitations of Thing Descriptions which we've experienced before:
If we conclude that action queues are in fact too complex to describe in a static Thing Description, we could either:
|
@benfrancis many thanks for your examples and contributions to this topic. I am not very happy about the very verbose TD either. I'm loud thinking if it makes sense to define a subprotocol which is assumed as default in TD definitions. Maybe this can be also reused for the WoT Profile.
This problem may be solved if we have a behavioral assertion in the proposed subprotocol definition.
Good question. Maybe @handrews can help us here.
Lets discuss this again in today's TD call. Another idea would be to replace "instance" by "byid". E.g., |
Add queryaction and cancelaction operations - closes #302
In Mozilla's Web Thing API, an action can be requested using an HTTP
POST
request on an Action resource to create an ActionRequest resource. The Action resource is essentially an action queue, consisting of multiple ActionRequest resources.The response to the
POST
provides a unique URL for the ActionRequest resource, which can then have its status queried with aGET
or be cancelled with aDELETE
. A list of all current requests can be retrieved by aGET
on the Action resource.How would this API be described in a Thing Description following the current draft specification? Or is there another intended way to achieve these use cases?
The text was updated successfully, but these errors were encountered: