mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-29 13:49:28 +08:00

* Introduce Config response for bootstrap procedure Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add inital service implementation Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Enable status change Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Fix logger import Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update BSS to send config in valid format Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use ConfigReader to create valid format response Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update config retrieval error handle Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Enable Thing deletion API Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add API support for fetching Thing by ID Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add list Things endpoint Update database schema Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use MF API to update status Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use Channels list Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Fix reading Thing from the database Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Create Mainflux Thing when adding new Thing to BS Create MF Thing as soon as Bootstrap service thing is added. There are 2 main reasons to create Thing when adding a new BS Thing over creating Thing on bootstrapping: 1) On bootstrapping time, user JWT will not be sent as a part of request, so there is no mechanism to send a valid API call to Mainflux. 2) This way, Bootstrap service will be in sync with Mainlux: each Thing existing in BS will also be in Mainflux. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add Thing update Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove API key from BS service Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Improve channels update algorithm Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Refactor code Remove unused fields, comment code and simplfy some method signatures. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove Identity Provider and use gRPC Update dependencies Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add external auth key Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update BS config reader Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update docker-compose Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update env variable read Add MQTT password to bootstrap response. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update response fields and tags Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove status check Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Enable BS of active Things Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add NewThing state Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Rename Status to State Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update README.md Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add filterng Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update List endpoint Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Fix Database query Remove copyright headers. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add filter type Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Gateway provisioning (1.d) Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update self-bootstrapping feature Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add mocks Update dependencies to the newest Mainflux version. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add thing service tests Mocks fix. Some of the service code intentionally left untested due to possible changes in future. Fix copyright headers and update Mainflux and other dependencies. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use name "Config" instead of "Thing" Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Refactor code Remove commented code. Fix typo. Remove unused exported error. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Simplify service tests Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove Assign method Raise test coverage. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update database schema Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Store unknown bootstrap attempts Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update unknown bootstrap handling Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update naming Fix uses of `Thing` in DB and `api` package. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add endpoint tests Currently, only test for adding a new Config are implemented. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add initialization of DB tests Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add DB tests Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update readme file Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add API docs Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove Mainflux from vendor Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add licence headers Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Fix service and endpoint tests Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Raise test coverage Remove unused repsonse type. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update build and deployment Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update API docs Fix typo. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update imports formatting Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Make state response empty Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Raise test coverage Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update API docs Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update readme file Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use uuid as a primary key Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use Mainflux ID Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Remove `Created` state. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Move State to separate file Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Add Things prefix Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update API and API docs Be consistent in API naming and add some useful comments. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Refactor repository implementation Cleanup code, make it more readable. Fix missing drop in migrations. Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Use "cfg" insted of "thing" Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Fix tests Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com> * Update tables names Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
307 lines
7.4 KiB
Go
307 lines
7.4 KiB
Go
//
|
|
// Copyright (c) 2018
|
|
// Mainflux
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/mainflux/mainflux"
|
|
mfsdk "github.com/mainflux/mainflux/sdk/go"
|
|
)
|
|
|
|
const (
|
|
thingType = "device"
|
|
chanName = "channel"
|
|
)
|
|
|
|
var (
|
|
// ErrNotFound indicates a non-existent entity request.
|
|
ErrNotFound = errors.New("non-existent entity")
|
|
|
|
// ErrMalformedEntity indicates malformed entity specification.
|
|
ErrMalformedEntity = errors.New("malformed entity specification")
|
|
|
|
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
|
|
// when accessing a protected resource.
|
|
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
|
|
|
// ErrConflict indicates that entity with the same ID or external ID already exists.
|
|
ErrConflict = errors.New("entity already exists")
|
|
|
|
// ErrThings indicates failure to communicate with Mainflux Things service.
|
|
// It can be due to networking error or invalid/unauthorized request.
|
|
ErrThings = errors.New("error receiving response from Things service")
|
|
)
|
|
|
|
var _ Service = (*bootstrapService)(nil)
|
|
|
|
// Service specifies an API that must be fulfilled by the domain service
|
|
// implementation, and all of its decorators (e.g. logging & metrics).
|
|
type Service interface {
|
|
// Add adds new Thing to the user identified by the provided key.
|
|
Add(string, Config) (Config, error)
|
|
|
|
// View returns Thing with given ID belonging to the user identified by the given key.
|
|
View(string, string) (Config, error)
|
|
|
|
// Update updates editable fields of the provided Thing.
|
|
Update(string, Config) error
|
|
|
|
// List returns subset of Things with given state that belong to the user identified by the given key.
|
|
List(string, Filter, uint64, uint64) ([]Config, error)
|
|
|
|
// Remove removes Thing with specified key that belongs to the user identified by the given key.
|
|
Remove(string, string) error
|
|
|
|
// Bootstrap returns configuration to the Thing with provided external ID using external key.
|
|
Bootstrap(string, string) (Config, error)
|
|
|
|
// ChangeState changes state of the Thing with given ID and owner.
|
|
ChangeState(string, string, State) error
|
|
}
|
|
|
|
// ConfigReader is used to parse Config into format which will be encoded
|
|
// as a JSON and consumed from the client side. The purpose of this interface
|
|
// is to provide convenient way to generate custom configuration response
|
|
// based on the specific Config which will be consumed by the client.
|
|
type ConfigReader interface {
|
|
ReadConfig(Config) (mainflux.Response, error)
|
|
}
|
|
|
|
type bootstrapService struct {
|
|
users mainflux.UsersServiceClient
|
|
configs ConfigRepository
|
|
sdk mfsdk.SDK
|
|
}
|
|
|
|
// New returns new Bootstrap service.
|
|
func New(users mainflux.UsersServiceClient, configs ConfigRepository, sdk mfsdk.SDK) Service {
|
|
return &bootstrapService{
|
|
configs: configs,
|
|
sdk: sdk,
|
|
users: users,
|
|
}
|
|
}
|
|
|
|
func (bs bootstrapService) Add(key string, cfg Config) (Config, error) {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
// Check if channels exist. This is the way to prevent invalid configuration to be saved.
|
|
// However, channels deletion wil eventually cause this; since Bootstrap service is not
|
|
// using events from the Things service at the moment.
|
|
for _, c := range cfg.MFChannels {
|
|
if _, err := bs.sdk.Channel(c, key); err != nil {
|
|
return Config{}, ErrMalformedEntity
|
|
}
|
|
}
|
|
mfThing, err := bs.add(key)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.MFThing = mfThing.ID
|
|
cfg.Owner = owner
|
|
cfg.State = Inactive
|
|
cfg.MFKey = mfThing.Key
|
|
|
|
id, err := bs.configs.Save(cfg)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
bs.configs.RemoveUnknown(cfg.ExternalKey, cfg.ExternalID)
|
|
|
|
cfg.MFThing = id
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) View(key, id string) (Config, error) {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
return bs.configs.RetrieveByID(owner, id)
|
|
}
|
|
|
|
func (bs bootstrapService) Update(key string, cfg Config) error {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg.Owner = owner
|
|
|
|
t, err := bs.configs.RetrieveByID(owner, cfg.MFThing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id := t.MFThing
|
|
var connect []string
|
|
var disconnect map[string]bool
|
|
|
|
switch t.State {
|
|
case Active:
|
|
disconnect = make(map[string]bool, len(t.MFChannels))
|
|
for _, c := range t.MFChannels {
|
|
disconnect[c] = true
|
|
}
|
|
|
|
for _, c := range cfg.MFChannels {
|
|
if cfg.State == Active {
|
|
if disconnect[c] {
|
|
// Don't disconnect common elements.
|
|
delete(disconnect, c)
|
|
continue
|
|
}
|
|
// Connect new elements.
|
|
connect = append(connect, c)
|
|
}
|
|
}
|
|
|
|
default:
|
|
if cfg.State == Active {
|
|
// Connect all new elements.
|
|
connect = cfg.MFChannels
|
|
}
|
|
}
|
|
|
|
for c := range disconnect {
|
|
if err := bs.sdk.DisconnectThing(id, c, key); err != nil {
|
|
if err == mfsdk.ErrNotFound {
|
|
return ErrMalformedEntity
|
|
}
|
|
return ErrThings
|
|
}
|
|
}
|
|
|
|
for _, c := range connect {
|
|
if err := bs.sdk.ConnectThing(id, c, key); err != nil {
|
|
if err == mfsdk.ErrNotFound {
|
|
return ErrMalformedEntity
|
|
}
|
|
return ErrThings
|
|
}
|
|
}
|
|
|
|
return bs.configs.Update(cfg)
|
|
}
|
|
|
|
func (bs bootstrapService) List(key string, filter Filter, offset, limit uint64) ([]Config, error) {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return []Config{}, err
|
|
}
|
|
if filter == nil {
|
|
return []Config{}, ErrMalformedEntity
|
|
}
|
|
if _, ok := filter["unknown"]; ok {
|
|
return bs.configs.RetrieveUnknown(offset, limit), nil
|
|
}
|
|
|
|
return bs.configs.RetrieveAll(owner, filter, offset, limit), nil
|
|
}
|
|
|
|
func (bs bootstrapService) Remove(key, id string) error {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
thing, err := bs.configs.RetrieveByID(owner, id)
|
|
if err != nil {
|
|
if err == ErrNotFound {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := bs.sdk.DeleteThing(thing.MFThing, key); err != nil {
|
|
return ErrThings
|
|
}
|
|
|
|
return bs.configs.Remove(owner, id)
|
|
}
|
|
|
|
func (bs bootstrapService) Bootstrap(externalKey, externalID string) (Config, error) {
|
|
thing, err := bs.configs.RetrieveByExternalID(externalKey, externalID)
|
|
if err != nil {
|
|
if err == ErrNotFound {
|
|
bs.configs.SaveUnknown(externalKey, externalID)
|
|
}
|
|
return Config{}, ErrNotFound
|
|
}
|
|
|
|
return thing, nil
|
|
}
|
|
|
|
func (bs bootstrapService) ChangeState(key, id string, state State) error {
|
|
owner, err := bs.identify(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
thing, err := bs.configs.RetrieveByID(owner, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if thing.State == state {
|
|
return nil
|
|
}
|
|
|
|
switch state {
|
|
case Active:
|
|
for _, c := range thing.MFChannels {
|
|
if err := bs.sdk.ConnectThing(thing.MFThing, c, key); err != nil {
|
|
return ErrThings
|
|
}
|
|
}
|
|
case Inactive:
|
|
for _, c := range thing.MFChannels {
|
|
if err := bs.sdk.DisconnectThing(thing.MFThing, c, key); err != nil {
|
|
if err == mfsdk.ErrNotFound {
|
|
continue
|
|
}
|
|
return ErrThings
|
|
}
|
|
}
|
|
}
|
|
|
|
return bs.configs.ChangeState(owner, id, state)
|
|
}
|
|
|
|
func (bs bootstrapService) add(key string) (mfsdk.Thing, error) {
|
|
thingID, err := bs.sdk.CreateThing(mfsdk.Thing{Type: thingType}, key)
|
|
if err != nil {
|
|
return mfsdk.Thing{}, err
|
|
}
|
|
|
|
thing, err := bs.sdk.Thing(thingID, key)
|
|
if err != nil {
|
|
return mfsdk.Thing{}, bs.sdk.DeleteThing(thingID, key)
|
|
}
|
|
|
|
return thing, nil
|
|
}
|
|
|
|
func (bs bootstrapService) identify(token string) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
res, err := bs.users.Identify(ctx, &mainflux.Token{Value: token})
|
|
if err != nil {
|
|
return "", ErrUnauthorizedAccess
|
|
}
|
|
|
|
return res.GetValue(), nil
|
|
}
|