mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-26 13:48:53 +08:00
MF-539 - Improve Bootstrap Service documentation (#646)
* Fix infinte loop in Subscribe method Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update API docs Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update service README Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update docs Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Add bootstrapping flow gif Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update bootstrapping flow gif Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
This commit is contained in:
parent
61b2d6b87b
commit
2ed1471d5f
@ -1,22 +1,22 @@
|
||||
# BOOTSTRAP SERVICE
|
||||
|
||||
New devices need to be configured properly and connected to the Mainflux. Bootstrap service is used in order to accomplish that. This service provides the following features:
|
||||
1) Creating new Mainflux Things
|
||||
2) Providing basic configuration for the newly created Things
|
||||
3) Enabling/disabling Things
|
||||
1) Creating new Mainflux Things
|
||||
2) Providing basic configuration for the newly created Things
|
||||
3) Enabling/disabling Things
|
||||
|
||||
Pre-provisioning a new Thing is as simple as sending Thing data to the Bootstrap service. Once the Thing is active, it sends a request for initial config to Bootstrap service. Once the Thing is bootstrapped, it’s possible to add it to the whitelist, so that it can exchange messages using Mainflux. Bootstrapping does not implicitly enable Things, it has to be done manually.
|
||||
Pre-provisioning a new Thing is as simple as sending Configuration data to the Bootstrap service. Once the Thing is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Things. Only enabled Things can exchange messages over Mainflux. Bootstrapping does not implicitly enable Things, it has to be done manually.
|
||||
|
||||
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during manufacturing process. If the Thing is provisioned on the Bootstrap service side, corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
|
||||
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
|
||||
|
||||
***Thing Configuration***
|
||||
|
||||
Thing Configuration consists of two logical parts: custom configuration (that can be interpreted by the Thing itself) and Mainflux-related configuration. Mainflux config contains:
|
||||
1) corresponding Mainflux Thing ID
|
||||
2) corresponding Mainflux Thing key
|
||||
3) list of the Mainflux channels the Thing is connected to
|
||||
Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Mainflux-related configuration. Mainflux config contains:
|
||||
1) corresponding Mainflux Thing ID
|
||||
2) corresponding Mainflux Thing key
|
||||
3) list of the Mainflux channels the Thing is connected to
|
||||
|
||||
>Note: list of channels contains IDs of the Mainflux channels. These channels are _pre-provisioned_ on the Mainflux side and, unlike corresponding Mainflux Thing, Bootstrap service does not create Mainflux Channels.
|
||||
>Note: list of channels contains IDs of the Mainflux channels. These channels are _pre-provisioned_ on the Mainflux side and, unlike corresponding Mainflux Thing, Bootstrap service is not able to create Mainflux Channels.
|
||||
|
||||
Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as connecting corresponding Mainflux Thing to the given list of Channels. Configuration keeps _state_ of the Thing:
|
||||
|
||||
@ -51,6 +51,10 @@ The service is configured using the environment variables presented in the follo
|
||||
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
|
||||
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
|
||||
| MF_USERS_URL | Users service URL | localhost:8181 |
|
||||
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
|
||||
| MF_THINGS_ES_PASS | Things service event source password | |
|
||||
| MF_THINGS_ES_DB | Things service event source database | 0 |
|
||||
| MF_BOOTSTRAP_INSTANCE_NAME | Bootstrap service instance name | bootstrap |
|
||||
|
||||
## Deployment
|
||||
|
||||
@ -87,7 +91,11 @@ version: "2"
|
||||
MF_SDK_BASE_URL: [Base SDK URL for the Mainflux services]
|
||||
MF_SDK_THINGS_PREFIX: [SDK prefix for Things service]
|
||||
MF_USERS_URL: [Users service URL]
|
||||
```
|
||||
MF_THINGS_ES_URL: [Things service event source URL]
|
||||
MF_THINGS_ES_PASS: [Things service event source password]
|
||||
MF_THINGS_ES_DB: [Things service event source database]
|
||||
MF_BOOTSTRAP_INSTANCE_NAME: [Bootstrap service instance name]
|
||||
```
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
|
@ -19,12 +19,14 @@ const (
|
||||
channelPrefix = "channel."
|
||||
channelUpdate = channelPrefix + "update"
|
||||
channelRemove = channelPrefix + "remove"
|
||||
|
||||
exists = "BUSYGROUP Consumer Group name already exists"
|
||||
)
|
||||
|
||||
// EventStore represents event source for things and channels provisioning.
|
||||
type EventStore interface {
|
||||
// Subscribes to given subject and receives events.
|
||||
Subscribe(string)
|
||||
Subscribe(string) error
|
||||
}
|
||||
|
||||
type eventStore struct {
|
||||
@ -44,8 +46,12 @@ func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string,
|
||||
}
|
||||
}
|
||||
|
||||
func (es eventStore) Subscribe(subject string) {
|
||||
es.client.XGroupCreateMkStream(stream, group, "$").Err()
|
||||
func (es eventStore) Subscribe(subject string) error {
|
||||
err := es.client.XGroupCreateMkStream(stream, group, "$").Err()
|
||||
if err != nil && err.Error() != exists {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
streams, err := es.client.XReadGroup(&redis.XReadGroupArgs{
|
||||
Group: group,
|
||||
|
@ -408,6 +408,9 @@ definitions:
|
||||
external_key:
|
||||
type: string
|
||||
description: External key.
|
||||
thing_id:
|
||||
type: string
|
||||
description: ID of the corresponding Mainflux Thing.
|
||||
channels:
|
||||
type: array
|
||||
minItems: 0
|
||||
|
@ -50,11 +50,10 @@ const (
|
||||
defBaseURL = "http://localhost"
|
||||
defThingsPrefix = ""
|
||||
defUsersURL = "localhost:8181"
|
||||
|
||||
defESURL = "localhost:6379"
|
||||
defESPass = ""
|
||||
defESDB = "0"
|
||||
defInstanceName = "bootstrap"
|
||||
defESURL = "localhost:6379"
|
||||
defESPass = ""
|
||||
defESDB = "0"
|
||||
defInstanceName = "bootstrap"
|
||||
|
||||
envLogLevel = "MF_BOOTSTRAP_LOG_LEVEL"
|
||||
envDBHost = "MF_BOOTSTRAP_DB_HOST"
|
||||
@ -74,11 +73,10 @@ const (
|
||||
envBaseURL = "MF_SDK_BASE_URL"
|
||||
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
|
||||
envUsersURL = "MF_USERS_URL"
|
||||
|
||||
envESURL = "MF_THINGS_ES_URL"
|
||||
envESPass = "MF_THINGS_ES_PASS"
|
||||
envESDB = "MF_THINGS_ES_DB"
|
||||
envInstanceName = "MF_BOOTSTRAP_INSTANCE_NAME"
|
||||
envESURL = "MF_THINGS_ES_URL"
|
||||
envESPass = "MF_THINGS_ES_PASS"
|
||||
envESDB = "MF_THINGS_ES_DB"
|
||||
envInstanceName = "MF_BOOTSTRAP_INSTANCE_NAME"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
@ -262,5 +260,7 @@ func startHTTPServer(svc bootstrap.Service, cfg config, logger logger.Logger, er
|
||||
func subscribeToThingsES(svc bootstrap.Service, client *r.Client, consumer string, logger logger.Logger) {
|
||||
eventStore := redis.NewEventStore(svc, client, consumer, logger)
|
||||
logger.Info("Subscribed to Redis Event Store")
|
||||
eventStore.Subscribe("mainflux.things")
|
||||
if err := eventStore.Subscribe("mainflux.things"); err != nil {
|
||||
logger.Warn(fmt.Sprintf("Botstrap service failed to subscribe to event sourcing: %s", err))
|
||||
}
|
||||
}
|
||||
|
90
docs/bootstrap.md
Normal file
90
docs/bootstrap.md
Normal file
@ -0,0 +1,90 @@
|
||||
## Bootstrap
|
||||
|
||||
`Bootstrapping` refers to a self-starting process that is supposed to proceed without external input.
|
||||
Mainflux platform supports bootstrapping process, but some of the preconditions need to be fulfilled in advance. The device can trigger a bootstrap when:
|
||||
- device contains only bootstrap credentials and no Mainflux credentials
|
||||
- device, for any reason, fails to start a communication with the configured Mainflux services (server not responding, authentication failure, etc..).
|
||||
- device, for any reason, wants to update its configuration
|
||||
|
||||
> Bootstrapping and provisioning are two different procedures. Provisioning refers to entities management while bootstrapping is related to entity configuration.
|
||||
|
||||

|
||||
|
||||
### Configuration
|
||||
|
||||
The configuration of Mainflux thing consists of two major parts:
|
||||
|
||||
- The list of Mainflux channels the thing is connected to
|
||||
- Custom configuration related to the specific thing
|
||||
|
||||
Also, the configuration contains an external ID and external key, which will be explained later.
|
||||
In order to enable the thing to start bootstrapping process, the user needs to upload a valid configuration for that specific thing. This can be done using the following HTTP request:
|
||||
|
||||
```
|
||||
curl -s -S -i -X POST -H "Authorization: <user_token>" -H "Content-Type: application/json" http://localhost:8900/things/configs -d '{
|
||||
"external_id":"09:6:0:sb:sa",
|
||||
"thing_id": "1b9b8fae-9035-4969-a240-7fe5bdc0ed28",
|
||||
"external_key":"key",
|
||||
"name":"some",
|
||||
"channels":[
|
||||
"c3642289-501d-4974-82f2-ecccc71b2d83",
|
||||
"cd4ce940-9173-43e3-86f7-f788e055eb14",
|
||||
"ff13ca9c-7322-4c28-a25c-4fe5c7b753fc",
|
||||
"c3642289-501d-4974-82f2-ecccc71b2d82"
|
||||
],
|
||||
"content": "config..."
|
||||
}'
|
||||
```
|
||||
|
||||
In this example, `channels` field represents the list of Mainflux channel IDs the thing is connected to. These channels need to be provisioned before the configuration is uploaded. Field `content` represents custom configuration. This custom configuration contains parameters that can be used to set up the thing. It can also be empty if no additional set up is needed. Field `name` is human readable name and `thing_id` is an ID of the Mainflux thing. This field is not required. If `thing_id` is empty, corresponding Mainflux thing will be created implicitly and its ID will be sent as a part of `Location` header of the response.
|
||||
|
||||
There are two more fields: `external_id` and `external_key`. External ID represents an ID of the device that corresponds to the given thing. For example, this can be a MAC address or the serial number of the device. The external key represents the device key. This is the secret key that's safely stored on the device and it is used to authorize the thing during the bootstrapping process. Please note that external ID and external key and Mainflux ID and Mainflux key are _completely different concepts_. External id and key are only used to authenticate a device that corresponds to the specific Mainflux thing during the bootstrapping procedure.
|
||||
|
||||
### Bootstrapping
|
||||
|
||||
Currently, the bootstrapping procedure is executed over the HTTP protocol. Bootstrapping is nothing else but fetching and applying the configuration that corresponds to the given Mainflux thing. In order to fetch the configuration, _the thing_ needs to send a bootstrapping request:
|
||||
|
||||
```
|
||||
curl -s -S -i -H "Authorization: <external_key>" http://localhost:8900/things/bootstrap/<external_id>
|
||||
```
|
||||
|
||||
The response body should look something like:
|
||||
|
||||
```
|
||||
{
|
||||
"mainflux_id":"7c9df5eb-d06b-4402-8c1a-df476e4394c8",
|
||||
"mainflux_key":"86a4f870-eba4-46a0-bef9-d94db2b64392",
|
||||
"mainflux_channels":[
|
||||
{
|
||||
"id":"ff13ca9c-7322-4c28-a25c-4fe5c7b753fc",
|
||||
"name":"some channel",
|
||||
"metadata":{
|
||||
"operation":"someop",
|
||||
"type":"metadata"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id":"925461e6-edfb-4755-9242-8a57199b90a5",
|
||||
"name":"channel1",
|
||||
"metadata":{
|
||||
"type":"control"
|
||||
}
|
||||
}
|
||||
],
|
||||
"content":"config..."
|
||||
}
|
||||
```
|
||||
|
||||
The response consists of an ID and key of the Mainflux thing, the list of channels and custom configuration (`content` field). The list of channels contains not just channel IDs, but the additional Mainflux channel data (`name` and `metadata` fields), as well.
|
||||
|
||||
### Enabling and disabling things
|
||||
|
||||
Uploading configuration does not automatically connect thing to the given list of channels. In order to connect the thing to the channels, user needs to send the following HTTP request:
|
||||
|
||||
```
|
||||
curl -s -S -i -X PUT -H "Authorization: <user_token>" -H "Content-Type: application/json" http://localhost:8900/things/state/<thing_id> -d '{"state": 1}'
|
||||
```
|
||||
|
||||
In order to disconnect, the same request should be sent with the value of `state` set to 0.
|
||||
|
||||
For more information about Bootstrap API, please check out the [API documentation](https://github.com/mainflux/mainflux/blob/master/bootstrap/swagger.yml).
|
BIN
docs/img/bs_flow.gif
Normal file
BIN
docs/img/bs_flow.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 299 KiB |
@ -35,5 +35,6 @@ pages:
|
||||
- LoRa: lora.md
|
||||
- TLS: tls.md
|
||||
- CLI: cli.md
|
||||
- Bootstrap: bootstrap.md
|
||||
- Developer's Guide: dev-guide.md
|
||||
- Load Test: load-test.md
|
||||
|
Loading…
x
Reference in New Issue
Block a user