OpenMTC/doc/sdk-client.md
2017-11-07 14:41:38 +01:00

686 lines
23 KiB
Markdown

# SDK - The low-level CSE Client
## Introduction
The OpenMTC SDK offers a client module for low-level access to the
oneM2M resource tree exposed by a CSE's reference point. Currently,
only the http and https protocols are supported.
Basically, there are different types of Common Service Entities (CSE):
* MN-CSE: Middle Node CSE (OpenMTC Gateway)
* IN-CSE: Infrastructure Node CSE (OpenMTC Backend)
The client module comprises classes for representing requests,
responses as well as classes that provide an abstraction for a
connection to a CSE's reference point (the actual client itself).
## Requests
Requests to a CSE are called a OneM2MRequest. The OpenMTC SDK provides
this class for representing the different types of requests that can
be issued towards a CSE. This class resides under the
`openmtc_onem2m.transport` package. The following requests
(OneM2MOperation) are available:
* `retrieve`
* `delete`
* `create`
* `notify`
* `update`
### OneM2MRequest - Retrieve
The most trivial case of a `OneM2MRequest` is the `retrieve`. It takes
the path of the resource to be retrieved as parameter upon
construction.
This file can be found [here](./training/onem2m-examples/onem2m-example-3.py).
``` py
# Example 3: Retrieve OneM2MRequest
from openmtc_onem2m.transport import OneM2MRequest
request = OneM2MRequest("retrieve", to="onem2m")
print request.to
#>>> onem2m
```
### OneM2MRequest - Delete
Like the `retrieve` `OneM2MRequest`, a `delete` `OneM2MRequest` merely
takes the path of the resource to be deleted as parameter upon
construction.
This file can be found [here](./training/onem2m-examples/onem2m-example-4.py).
``` py
# Example 4: Delete OneM2MRequest
from openmtc_onem2m.transport import OneM2MRequest
request = OneM2MRequest("delete", to="onem2m")
print request.to
#>>> onem2m
```
### OneM2MRequest - Create
When creating a `create` `OneM2MRequest` object we need to specify the
object to be created together with the path where it is to be
created. In most cases this is done by creating an appropriate
resource object and passing it.
This file can be found [here](./training/onem2m-examples/onem2m-example-5a.py).
``` py
# Example 5a: Create OneM2MRequest
from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE
my_app = AE(App_ID="myApp")
request = OneM2MRequest("create", to="onem2m", pc="my_app")
print request.to
#>>> onem2m
print request.pc
#>>> myApp
```
When creating contentInstances, we can also pass in a string of raw
data. In this case, we also need to specify the mime-type of the data
via the `resource_type` parameter (ty).
This file can be found [here](./training/onem2m-examples/onem2m-example-5b.py).
``` py
# Example 5b: Create OneM2MRequest with data
from openmtc_onem2m.transport import OneM2MRequest
import json
sensor_data = {"type": "temperature",
"value": 15 }
data_string = json.dumps(sensor_data)
request = OneM2MRequest("create",
to="onem2m",
pc=data_string,
ty="application/json")
print request.to
#>>> onem2m
print request.pc
#>>> {"type": "temperature", "value": 15}
```
### OneM2MRequest - Notify
For `notify` `OneM2MRequest` objects the same semantics as for
`create` `OneM2MRequest` apply.
This file can be found [here](./training/onem2m-examples/onem2m-example-6a.py).
``` py
# Example 6a: Notify OneM2MRequest
from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE
my_app = AE(App_ID="myApp")
request = OneM2MRequest("notify", to="onem2m", pc=my_app)
print request.to
#>>> onem2m
print request.pc.App_ID
#>>> myApp
```
This file can be found [here](./training/onem2m-examples/onem2m-example-6b.py).
``` py
# Example 6b: Notify OneM2MRequest with data
from openmtc_onem2m.transport import OneM2MRequest
import json
sensor_data = {"type": "temperature",
"value": 15 }
data_string = json.dumps(sensor_data)
request = OneM2MRequest("create",
to="onem2m",
pc=data_string,
ty="application/json")
print request.to
#>>> onem2m
print request.pc
#>>> {"type": "temperature", "value": 15}
```
### OneM2MRequest - Update
The `update` `OneM2MRequest` can be used to update specific attributes
of an object (AE). If the request is legal, four different cases are
distinguished:
* If an attribute value **is provided** in the `OneM2MRequest` that
**exists** in the target resource, the CSE will simply update that
attribute in the resource representation.
* If an attribute **is not provided** in the `OneM2MRequest`, but the
attribute **exists** in the target resource, the hosting CSE will
simply leave the value of that attribute unchanged.
* If an attribute **is provided** in the `OneM2MRequest` and does
**not exist** in the target resource, the hosting CSE will create
such attribute with the provided value.
* If an attribute is **set to NULL** in the `OneM2MRequest` and
**exists** in the target resource, the hosting CSE will delete such
attribute if the deletion of the attribute is allowed by the local
policy.
The following example shows the creation of a `update`
`OneM2MRequest`. The CSE would either update the attribute (labels) in
the resource representation if it exists already exists there, or
create the attribute labels with the provided value if it does not
exist in the CSE resource representation yet.
This file can be found [here](./training/onem2m-examples/onem2m-example-7.py).
``` py
# Example 7: Update OneM2MRequest
from openmtc_onem2m.transport import OneM2MRequest
from openmtc_onem2m.model import AE
my_app = AE(App_ID="myApp", labels=["keyword1", "keyword2"])
request = OneM2MRequest("update", to="onem2m", pc=my_app.labels)
print request.to
#>>> onem2m
print request.pc
#>>> [u'keyword1', u'keyword2']
```
## Responses
Upon servicing a request, a CSE will return a `OneM2MResponse`, which
is a class of the client module. This class is defined in the
`openmtc_onem2m.transport` module and derives from the `object` base
class. The following response types are possible:
* `Create`
* `Retrieve`
* `Update`
* `Delete`
* `Notify`
* `Execute`
* `Observe`
An `OneM2MResponse` has the following properties:
* `status_code` - Denotes the result status of the operation (see
below).
* `request` - The type of the operation. One of (`create`, `retrieve`,
`update`, `delete`, `notify`, `execute`, `observe`).
* `rqi` - Denotes the request identifier (`requestIdentifier`).
* `pc` - Denotes the resource content (`primitiveContent`).
* `to` - Denotes to destination of the response.
### Error Responses
If an error occurs on the CSE servicing the request, the CSE will
return a `OneM2MErrorResponse`. Note that the `OneM2MErrorResponse`
class is an `Exception`. In case that any error is reported by the CSE
during processing a request, the client will *raise* an instance of
`OneM2MErrorResponse`. The `OneM2MErrorResponse` heritates from the
classes `OneM2MResponse` and `OneM2MError` (`OpenMTCError`) and is not
yet implemented (pass).
### Status Codes
The `status_code` of `OneM2MResponse` objects are defined as constants
in the `openmtc_onem2m.exc` module. The following constants are
defined:
| ``STATUS`` | numeric_code | http_status_code |
|:-----------|:------------:|:----------------:|
| ``STATUS_ACCEPTED`` | 1000 | 202 |
| ``STATUS_OK`` | 2000 | 200 |
| ``STATUS_CREATED`` | 2001 | 201 |
| ``STATUS_BAD_REQUEST`` | 4000 | 400 |
| ``STATUS_NOT_FOUND`` | 4004 | 404 |
| ``STATUS_OPERATION_NOT_ALLOWED`` | 4005 | 405 |
| ``STATUS_REQUEST_TIMEOUT`` | 4008 | 408 |
| ``STATUS_SUBSCRIPTION_CREATOR_HAS_NO_PRIVILEGE`` | 4101 | 403 |
| ``STATUS_CONTENTS_UNACCEPTABLE`` | 4102 | 400 |
| ``STATUS_ACCESS_DENIED`` | 4103 | 403 |
| ``STATUS_GROUP_REQUEST_IDENTIFIER_EXISTS`` | 4104 | 409 |
| ``STATUS_CONFLICT`` | 4015 | 409 |
| ``STATUS_INTERNAL_SERVER_ERROR`` | 5000 | 500 |
| ``STATUS_NOT_IMPLEMENTED`` | 5001 | 501 |
| ``STATUS_TARGET_NOT_REACHABLE`` | 5103 | 404 |
| ``STATUS_NO_PRIVILEGE`` | 5105 | 403 |
| ``STATUS_ALREADY_EXISTS`` | 5106 | 403 |
| ``STATUS_TARGET_NOT_SUBSCRIBABLE`` | 5203 | 403 |
| ``STATUS_SUBSCRIPTION_VERIFICATION_INITIATION_FAILED`` | 5204 | 500 |
| ``STATUS_SUBSCRIPTION_HOST_HAS_NO_PRIVILEGE`` | 5205 | 403 |
| ``STATUS_NON_BLOCKING_REQUEST_NOT_SUPPORTED`` | 5206 | 501 |
| ``STATUS_EXTERNAL_OBJECT_NOT_REACHABLE`` | 6003 | 404 |
| ``STATUS_EXTERNAL_OBJECT_NOT_FOUND`` | 6005 | 404 |
| ``STATUS_MAX_NUMBER_OF_MEMBER_EXCEEDED`` | 6010 | 400 |
| ``STATUS_MEMBER_TYPE_INCONSISTENT`` | 6011 | 400 |
| ``STATUS_MANAGEMENT_SESSION_CANNOT_BE_ESTABLISHED`` | 6020 | 500 |
| ``STATUS_MANAGEMENT_SESSION_ESTABLISHMENT_TIMEOUT`` | 6021 | 500 |
| ``STATUS_INVALID_CMDTYPE`` | 6022 | 400 |
| ``STATUS_INVALID_ARGUMENTS`` | 6023 | 400 |
| ``STATUS_INSUFFICIENT_ARGUMENT`` | 6024 | 400 |
| ``STATUS_MGMT_CONVERSION_ERROR`` | 6025 | 500 |
| ``STATUS_CANCELLATION_FAILED`` | 6026 | 500 |
| ``STATUS_ALREADY_COMPLETE`` | 6028 | 400 |
| ``STATUS_COMMAND_NOT_CANCELLABLE`` | 6029 | 400 |
## Exceptions
In addition to raising an instance of `OneM2MErrorResponse`, the CSE
client might also inidcate error conditions that do not occur while
the CSE was processing the request. This will mainly happen when the
client was unable to contact the CSE for whatever reason.
Exeptions that are raised will be subclasses of the `OpenMTCError`
class defined in the `openmtc.exc` module.
## Using the Client
The client implementation for interfacing with the HTTP interface of
an CSE resides in the `openmtc_onem2m.client.http` module. The
implementing class is called `OneM2MHTTPClient`. In the current
version of the SDK, we simply import the class directly. This is
planned to be replaced with a more sophisticated factory pattern that
creates appropriate clients based on the transport scheme (e.g. `http`
or `mqtt`) that is used.
Client objects expose a method called `send_onem2m_request` for
sending `OneM2MRequest` objects to a CSE.
### Creating a Client
To create a client object, we simply import the `OneM2MHTTPClient`
class from the `openmtc_onem2m.client.http` module and create an
instance of it with the URI of a reference point of an oneM2M CSE.
This file can be found [here](./training/onem2m-examples/onem2m-example-8a.py).
``` py
# Example 8a: Creating a Client
from openmtc_onem2m.client.http import OneM2MHTTPClient
# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)
```
### Making Requests
To retrieve a resource from the CSE's resource tree, we can use the
`send_onem2m_request` method and pass an appropriate `OneM2MRequest`
object. In this case we retrieve the `CSEBase` resource of the CSE's
resource tree. If successful, the operation returns a promise, which
contains an `OneM2MResponse` object. The `OneM2MResponse` can be
obtained from the promise by using `.get()`. The `content` property of
the `OneM2MResponse` holds the appropriate `CSEBase` object.
This file can be found [here](./training/onem2m-examples/onem2m-example-8b.py).
``` py
# Example 8b: Making Requests
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest
# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)
# create a OneM2MRequest object
onem2m_request = OneM2MRequest("retrieve", to="onem2m")
# send the OneM2MRequest to the CSE
promise = client.send_onem2m_request(onem2m_request)
# reteive the OneM2MResponse from the returned promise
onem2m_response = promise.get()
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')
```
**Note:** This example (and most of the following ones) will only work
as shown, if a `gateway` instance is running in the background of the
localhost. This can be launched by running the
`openmtc-open-source/openmtc-gevent/run_gateway` script.
To create a resource on the CSE, we first create the desired resource
object and then send a create `OneM2MRequest`.
In the following example, we will add the optional pararmeter
`resourceName="MYAPP"` to the creation of the AE in order to
facilitate the retrieval of this AE in the browser. After execution of
the example (and the condition to have running CSE on the localhost)
the created AE on the CSE should be retrievable at URL
`http://localhost:8000/onem2m/MYAPP` in a browser on the
localhost. Further, we add the mandatory parameter
`requestReachability=False` which states, that the created AE should
have no server capability and therefore no reachability for other
instances.
For a `create` `OneM2MRequest`, there are two additional parameters:
`ty=AE` indicates that the resource that should be created on the CSE
is of type AE (ApplicationEntity). The statement `pc=my_app` specifies
what resource should be created on the CSE. In this case, it is the AE
created previously.
This file can be found [here](./training/onem2m-examples/onem2m-example-10.py).
``` py
# Example 10: Create a resource
from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest
# create a OneM2MHTTPClient object
client = OneM2MHTTPClient("http://localhost:8000", False)
# create a resource to be created on the CSE
# resourceName: (optional) for easy check in browser
# requestReachability: (mandatory) for servercapability of the AE
my_app = AE(App_ID="myApp",
labels=["keyword1", "keyword2"],
resourceName="MYAPP",
requestReachability=False)
# create a OneM2MRequest object of type 'create'
# ty: resource_type of the created resource
# pc: Resource content to be transferred
onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)
# send the 'create' OneM2MRequest to the CSE
promise = client.send_onem2m_request(onem2m_request)
# reteive the OneM2MResponse from the returned promise
onem2m_response = promise.get()
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2001, description='CREATED', http_status_code=201)
print onem2m_response.content
#>>> AE(path='None', id='ae0')
print onem2m_response.content.App_ID
#>>> myApp
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']
```
**Note:** If this example throws a `OneM2MErrorResponse` with
`response_status_code: STATUS(numeric_code=4015,
description='CONFLICT', http_status_code=409)`, then the
`resourceName` might already be registered at the CSE. Try to alter
the `resourceName`. ResourceNames need to be unique on the
CSE. Alternatively, the running CSE process can be terminated and
restarted. This avoids the need to change the `resourceName`.
**Note:** At this point the application object has been created in the
CSE's resource tree. However, the original object we created in our
program (`my_application`) has not been altered in any
way. Specifically, it does not contain any attributes that may have
been set or altered by the CSE, nor has its `path` property been set.
If we want to continue working with the application object it is good
practice to retrieve the object again through the resourceName.
This file can be found [here](./training/onem2m-examples/onem2m-example-11a.py).
``` py
# Example 11a: Create a resource (continued)
from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest
client = OneM2MHTTPClient("http://localhost:8000", False)
my_app = AE(App_ID="myApp",
labels=["keyword1", "keyword2"],
resourceName="MYAPP1",
requestReachability=False)
onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2001, description='CREATED', http_status_code=201)
# Build path to retieve from
path = "onem2m/" + onem2m_response.content.resourceName
print path
#>>> onem2m/MYAPP
# Retrieve the AE from the CSE
onem2m_request = OneM2MRequest("retrieve", to=path)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> AE(path='None', id='ae0')
# Set the local AE to the retrieved content
my_app = None
my_app = onem2m_response.content
print my_app.App_ID
#>>> myApp
print my_app.resourceName
#>>> MYAPP
print my_app.labels
#>>> [u'keyword1', u'keyword2']
```
**Note:** Again, if this example throws a `OneM2MErrorResponse` with
`response_status_code: STATUS(numeric_code=4015,
description='CONFLICT', http_status_code=409)`, then the
`resourceName` might already be registered at the CSE. Try to alter
the `resourceName`. Alternatively, the running CSE process can be
terminated and restarted. This avoids the need to change the
`resourceName`.
The following example showcases how to update some fields using
`OneM2MRequest` `update`.
This file can be found [here](./training/onem2m-examples/onem2m-example-11b.py).
``` py
# Example 11b: Updating a resource using OneM2MRequest Update
from openmtc_onem2m.model import AE
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest
client = OneM2MHTTPClient("http://localhost:8000", False)
my_app = AE(App_ID="myApp",
labels=["keyword1", "keyword2"],
resourceName="MYAPP2",
requestReachability=False)
# Create the AE 'my_app' at the CSE
onem2m_request = OneM2MRequest("create", to="onem2m", ty=AE, pc=my_app)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']
# Retrieve the AE from the CSE and check the labels
path = "onem2m/" + onem2m_response.content.resourceName
onem2m_request = OneM2MRequest("retrieve", to=path)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'keyword1', u'keyword2']
# Update the changes labels in the remote resource
# Therefore a temporay AE object is needed
# This temporary AE object should ONLY contian the fields that need to be updated
tmp_app = AE(labels=["foo", "bar", "coffee"])
onem2m_request = OneM2MRequest("update", to=path, pc=tmp_app)
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
print onem2m_response.content.labels
#>>> [u'foo', u'bar', u'coffee']
# Set the local AE to the retrieved content
my_app = None
my_app = onem2m_response.content
print my_app.labels
#>>> [u'foo', u'bar', u'coffee']
```
### Error Handling
The examples above have so far omitted error handling for the sake of
clarity and brevity. Obviously however, many things can go wrong at
various stages of processing and these cases need to be dealt with.
Any errors that are returned from the CSE will be represented in the
form of an `OneM2MErrorResponse` instance. As stated before, the
`OneM2MErrorResponse` class derives from `Exception`. Consequently,
`OneM2MErrorResponse` objects are not returned from the method,
instead they are raised as exceptions.
In addition, it is possible that the CSE could not be contacted at all
in the first place. In this case, an instance of
`openmtc.exc.ConnectionFailed` will be raised, which also derives from
`Exception`.
**Note:** This implies that whenever one of the client methods returns
normally, we can be sure that the operation has succeeded and continue
working with the result as planned without further inspecting the
result's status. This allows a very convenient and pythonic separation
of error and result handling.
With this in mind we can extend *Example 8b* by simply enclosing the
invocation of the client method in a `try`/`except`/`else` block.
This file can be found [here](./training/onem2m-examples/onem2m-example-12a.py).
``` py
# Example 12a: Making Requests with error handling
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest, OneM2MErrorResponse
from openmtc.exc import OpenMTCError
client = OneM2MHTTPClient("http://localhost:8000", False)
try:
onem2m_request = OneM2MRequest("retrieve", to="onem2m")
promise = client.send_onem2m_request(onem2m_request)
onem2m_response = promise.get()
except OneM2MErrorResponse as e:
print "CSE reported an error:", e
raise
except OpenMTCError as e:
print "Failed to reach the CSE:", e
raise
else:
pass
# no exception was raised, the method returned normally.
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')
```
### Forwarding
OpenMTC will automatically handle forwarding of a OneM2MRequest if it
is referring to a different CSE than the one the client is connected
to. Forwarding in OneM2M is based on CSE-IDs whereas the ETSI M2M
equivalent Retargeting is based on IPs.
Lets suppose that a gateway is availabe at `localhost:8000` and has
the CSE-ID `mn-cse-1`. Then, its backend is available at
`localhost:18000` and has the CSE-ID `in-cse-1`.
Due to forwarding, the following requests will have the same results:
* `localhost:8000/onem2m` and `localhost:18000/~/mn-cse-1/onem2m`
* `localhost:8000/onem2m` and `localhost:8000/~/mn-cse-1/onem2m"`
* `localhost:8000/~/in-cse-1/onem2m` and `localhost:18000/onem2m`
The following exaple illustrates this:
This file can be found [here](./training/onem2m-examples/onem2m-example-12b.py).
``` py
# Example 12b: Forwarding
from openmtc_onem2m.client.http import OneM2MHTTPClient
from openmtc_onem2m.transport import OneM2MRequest
client = OneM2MHTTPClient("http://localhost:8000", False)
onem2m_request = OneM2MRequest("retrieve", to="onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:8000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')
onem2m_request = OneM2MRequest("retrieve", to="~/mn-cse-1/onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:8000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> ~/mn-cse-1/onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')
client.port = 18000
onem2m_request = OneM2MRequest("retrieve", to="~/mn-cse-1/onem2m")
onem2m_response = client.send_onem2m_request(onem2m_request).get()
print "---> Request to: http://localhost:18000" + "/" + onem2m_request.to
print onem2m_response.to
#>>> ~/mn-cse-1/onem2m
print onem2m_response.response_status_code
#>>> STATUS(numeric_code=2000, description='OK', http_status_code=200)
print onem2m_response.content
#>>> CSEBase(path='None', id='cb0')
```