mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 23:32:14 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cacf5708b | |||
| ad5c66fad2 | |||
| d89e91143b | |||
| 96bcc4cff4 | |||
| a8eb7ae039 | |||
| 6eade2c1d0 | |||
| 148c1aca0a | |||
| 70517bd907 | |||
| d5f0d7d225 | |||
| b20a846071 | |||
| 6600d26ef1 | |||
| 3de34062db | |||
| 8e9d7b4fdb | |||
| 5757380a63 | |||
| c944205080 | |||
| d825174ccb | |||
| 6408b8a9d3 |
@@ -0,0 +1,15 @@
|
||||
Pull request title should be `MF-XXX - description` or `NOISSUE - description` where XXX is ID of issue that this PR relate to.
|
||||
Please review the [CONTRIBUTING.md](./CONTRIBUTING.md) file for detailed contributing guidelines.
|
||||
|
||||
### What does this do?
|
||||
|
||||
### Which issue(s) does this PR fix/relate to?
|
||||
Put here `Resolves #XXX` to auto-close the issue that your PR fixes (if such)
|
||||
|
||||
### List any changes that modify/break current functionality
|
||||
|
||||
### Have you included tests for your changes?
|
||||
|
||||
### Did you document any new/modified functionality?
|
||||
|
||||
### Notes
|
||||
@@ -1,6 +1,7 @@
|
||||
BUILD_DIR = build
|
||||
SERVICES = users things http normalizer ws influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader cli
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
GOOS ?= linux
|
||||
|
||||
@@ -9,12 +10,16 @@ define compile_service
|
||||
endef
|
||||
|
||||
define make_docker
|
||||
docker build --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
|
||||
docker build --no-cache --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
|
||||
endef
|
||||
|
||||
define make_docker_dev
|
||||
docker build --build-arg SVC_NAME=$(subst docker_dev_,,$(1)) --tag=mainflux/$(subst docker_dev_,,$(1)) -f docker/Dockerfile.dev ./build
|
||||
endef
|
||||
|
||||
all: $(SERVICES) mqtt
|
||||
|
||||
.PHONY: all $(SERVICES) dockers latest release mqtt
|
||||
.PHONY: all $(SERVICES) dockers dockers_dev latest release mqtt
|
||||
|
||||
clean:
|
||||
rm -rf ${BUILD_DIR}
|
||||
@@ -39,15 +44,24 @@ dockers: $(DOCKERS)
|
||||
docker build --tag=mainflux/dashflux -f dashflux/docker/Dockerfile dashflux
|
||||
docker build --tag=mainflux/mqtt -f mqtt/Dockerfile .
|
||||
|
||||
$(DOCKERS_DEV):
|
||||
$(call make_docker_dev,$(@))
|
||||
|
||||
dockers_dev: $(DOCKERS_DEV)
|
||||
|
||||
mqtt:
|
||||
cd mqtt && npm install
|
||||
|
||||
latest: dockers
|
||||
define docker_push
|
||||
for svc in $(SERVICES); do \
|
||||
docker push mainflux/$$svc; \
|
||||
docker push mainflux/$$svc:$(1); \
|
||||
done
|
||||
docker push mainflux/dashflux
|
||||
docker push mainflux/mqtt
|
||||
docker push mainflux/dashflux:$(1)
|
||||
docker push mainflux/mqtt:$(1)
|
||||
endef
|
||||
|
||||
latest: dockers
|
||||
$(call docker_push,latest)
|
||||
|
||||
release:
|
||||
$(eval version = $(shell git describe --abbrev=0 --tags))
|
||||
@@ -55,12 +69,13 @@ release:
|
||||
$(MAKE) dockers
|
||||
for svc in $(SERVICES); do \
|
||||
docker tag mainflux/$$svc mainflux/$$svc:$(version); \
|
||||
docker push mainflux/$$svc:$(version); \
|
||||
done
|
||||
docker tag mainflux/dashflux mainflux/dashflux:$(version)
|
||||
docker push mainflux/dashflux:$(version)
|
||||
docker tag mainflux/mqtt mainflux/mqtt:$(version)
|
||||
docker push mainflux/mqtt:$(version)
|
||||
$(call docker_push,$(version))
|
||||
|
||||
rundev:
|
||||
cd scripts && ./run.sh
|
||||
|
||||
run:
|
||||
cd scripts && ./run.sh
|
||||
docker-compose -f docker/docker-compose.yml up
|
||||
|
||||
+60
-54
@@ -8,16 +8,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const channelsEP = "channels"
|
||||
|
||||
var cmdChannels = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "create",
|
||||
@@ -25,10 +21,23 @@ var cmdChannels = []cobra.Command{
|
||||
Long: `Creates new channel and generates it's UUID`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateChannel(args[0], args[1])
|
||||
|
||||
var channel mfxsdk.Channel
|
||||
if err := json.Unmarshal([]byte(args[0]), &channel); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.CreateChannel(channel, args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(id)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -37,26 +46,52 @@ var cmdChannels = []cobra.Command{
|
||||
Long: `Gets list of all channels or gets channel by id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == "all" {
|
||||
GetChannels(args[1])
|
||||
l, err := sdk.Channels(args[1], uint64(Offset), uint64(Limit))
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(l)
|
||||
return
|
||||
}
|
||||
GetChannel(args[0], args[1])
|
||||
|
||||
c, err := sdk.Channel(args[0], args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(c)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <channel_id> <JSON_string> <user_auth_token>",
|
||||
Short: "update <JSON_string> <user_auth_token>",
|
||||
Long: `Updates channel record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
UpdateChannel(args[0], args[1], args[2])
|
||||
|
||||
var channel mfxsdk.Channel
|
||||
if err := json.Unmarshal([]byte(args[0]), &channel); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.UpdateChannel(channel, args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -65,63 +100,34 @@ var cmdChannels = []cobra.Command{
|
||||
Long: `Delete channel by ID`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DeleteChannel(args[0], args[1])
|
||||
|
||||
if err := sdk.DeleteChannel(args[0], args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewChannelsCmd returns channels command.
|
||||
func NewChannelsCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "channels",
|
||||
Short: "Manipulation with channels",
|
||||
Long: `Manipulation with channels: create, delete or update channels`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdChannels {
|
||||
for i := range cmdChannels {
|
||||
cmd.AddCommand(&cmdChannels[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateChannel - creates new channel and generates UUID
|
||||
func CreateChannel(data, token string) {
|
||||
url := fmt.Sprintf("%s/%s", serverAddr, channelsEP)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetChannels - gets all channels
|
||||
func GetChannels(token string) {
|
||||
url := fmt.Sprintf("%s/%s?offset=%s&limit=%s",
|
||||
serverAddr, channelsEP, strconv.Itoa(Offset), strconv.Itoa(Limit))
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetChannel - gets channel by ID
|
||||
func GetChannel(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// UpdateChannel - update a channel
|
||||
func UpdateChannel(id, data, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("PUT", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DeleteChannel - removes channel
|
||||
func DeleteChannel(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
const (
|
||||
defCertsPath = "/src/github.com/mainflux/mainflux/docker/ssl/certs/"
|
||||
envCertFile = "MF_CERT_FILE"
|
||||
envKeyFile = "MF_KEY_FILE"
|
||||
envCaFile = "MF_CA_FILE"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient = &http.Client{}
|
||||
serverAddr = fmt.Sprintf("https://%s", "localhost")
|
||||
|
||||
defCertFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "mainflux-server.crt")
|
||||
defKeyFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "mainflux-server.key")
|
||||
defCaFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "ca.crt")
|
||||
)
|
||||
|
||||
// SetServerAddr - set addr using host and port
|
||||
func SetServerAddr(proto string, host string, port int) {
|
||||
serverAddr = fmt.Sprintf("%s://%s", proto, host)
|
||||
|
||||
if port != 0 {
|
||||
serverAddr = fmt.Sprintf("%s:%s", serverAddr, strconv.Itoa(port))
|
||||
}
|
||||
}
|
||||
|
||||
func SetCerts() {
|
||||
// Set certificates paths
|
||||
certFile := mainflux.Env(envCertFile, defCertFile)
|
||||
keyFile := mainflux.Env(envKeyFile, defKeyFile)
|
||||
caFile := mainflux.Env(envCaFile, defCaFile)
|
||||
|
||||
// Load client cert
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load CA cert
|
||||
caCert, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
// Setup HTTPS client
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
transport := &http.Transport{TLSClientConfig: tlsConfig}
|
||||
httpClient = &http.Client{Transport: transport}
|
||||
}
|
||||
+12
-23
@@ -7,30 +7,32 @@
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
const contentTypeSenml = "application/senml+json"
|
||||
|
||||
var cmdMessages = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "send",
|
||||
Short: "send <channel_id> <JSON_string> <client_token>",
|
||||
Short: "send <channel_id> <JSON_string> <thing_token>",
|
||||
Long: `Sends message on the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
SendMsg(args[0], args[1], args[2])
|
||||
|
||||
if err := sdk.SendMessage(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewMessagesCmd returns messages command.
|
||||
func NewMessagesCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "msg",
|
||||
@@ -38,22 +40,9 @@ func NewMessagesCmd() *cobra.Command {
|
||||
Long: `Send or retrieve messages: control message flow on the channel`,
|
||||
}
|
||||
|
||||
for i, _ := range cmdMessages {
|
||||
for i := range cmdMessages {
|
||||
cmd.AddCommand(&cmdMessages[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// SendMsg - publishes SenML message on the channel
|
||||
func SendMsg(id, msg, token string) {
|
||||
url := serverAddr + "/http/channels/" + id + "/messages"
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(msg))
|
||||
LogError(err)
|
||||
|
||||
req.Header.Set("Authorization", token)
|
||||
req.Header.Add("Content-Type", contentTypeSenml)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
|
||||
// Keep SDK handle in global var
|
||||
var sdk mfxsdk.SDK
|
||||
|
||||
// SetSDK sets mainflux SDK instance.
|
||||
func SetSDK(s mfxsdk.SDK) {
|
||||
sdk = s
|
||||
}
|
||||
+77
-74
@@ -8,11 +8,9 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -25,26 +23,52 @@ var cmdThings = []cobra.Command{
|
||||
Long: `Create new thing, generate his UUID and store it`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateThing(args[0], args[1])
|
||||
|
||||
var thing mfxsdk.Thing
|
||||
if err := json.Unmarshal([]byte(args[0]), &thing); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.CreateThing(thing, args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(id)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get all/<thing_id> <user_auth_token>",
|
||||
Long: `Get all thingss or thing by id`,
|
||||
Long: `Get all things or thing by id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == "all" {
|
||||
GetThings(args[1])
|
||||
l, err := sdk.Things(args[1], uint64(Offset), uint64(Limit))
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
flush(l)
|
||||
return
|
||||
}
|
||||
GetThing(args[0], args[1])
|
||||
|
||||
t, err := sdk.Thing(args[0], args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(t)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -53,22 +77,40 @@ var cmdThings = []cobra.Command{
|
||||
Long: `Removes thing from database`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DeleteThing(args[0], args[1])
|
||||
|
||||
if err := sdk.DeleteThing(args[0], args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <thing_id> <JSON_string> <user_auth_token>",
|
||||
Short: "update <JSON_string> <user_auth_token>",
|
||||
Long: `Update thing record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
UpdateThing(args[0], args[1], args[2])
|
||||
|
||||
var thing mfxsdk.Thing
|
||||
if err := json.Unmarshal([]byte(args[0]), &thing); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.UpdateThing(thing, args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -77,10 +119,16 @@ var cmdThings = []cobra.Command{
|
||||
Long: `Connect thing to the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
ConnectThing(args[0], args[1], args[2])
|
||||
|
||||
if err := sdk.ConnectThing(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -89,79 +137,34 @@ var cmdThings = []cobra.Command{
|
||||
Long: `Disconnect thing to the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DisconnectThing(args[0], args[1], args[2])
|
||||
|
||||
if err := sdk.DisconnectThing(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewThingsCmd returns things command.
|
||||
func NewThingsCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "things",
|
||||
Short: "things <options>",
|
||||
Long: `Things handling: create, delete or update things.`,
|
||||
Long: `Things handling: create, delete or update things`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdThings {
|
||||
for i := range cmdThings {
|
||||
cmd.AddCommand(&cmdThings[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateThing - creates new thing and generates thing UUID
|
||||
func CreateThing(data, token string) {
|
||||
url := fmt.Sprintf("%s/%s", serverAddr, thingsEP)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetThings - gets all things
|
||||
func GetThings(token string) {
|
||||
url := fmt.Sprintf("%s/%s?offset=%s&limit=%s",
|
||||
serverAddr, thingsEP, strconv.Itoa(Offset), strconv.Itoa(Limit))
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetThing - gets thing by ID
|
||||
func GetThing(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// UpdateThing - updates thing by ID
|
||||
func UpdateThing(id, data, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("PUT", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DeleteThing - removes thing
|
||||
func DeleteThing(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// ConnectThing - connect thing to a channel
|
||||
func ConnectThing(cliId, chanId, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", serverAddr, channelsEP,
|
||||
chanId, thingsEP, cliId)
|
||||
req, err := http.NewRequest("PUT", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DisconnectThing - connect thing to a channel
|
||||
func DisconnectThing(cliId, chanId, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", serverAddr, channelsEP,
|
||||
chanId, thingsEP, cliId)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
+29
-25
@@ -8,9 +8,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -21,10 +19,20 @@ var cmdUsers = []cobra.Command{
|
||||
Long: `Creates new user`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateUser(args[0], args[1])
|
||||
|
||||
user := mfxsdk.User{
|
||||
Email: args[0],
|
||||
Password: args[1],
|
||||
}
|
||||
if err := sdk.CreateUser(user); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
@@ -33,43 +41,39 @@ var cmdUsers = []cobra.Command{
|
||||
Long: `Creates new token`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateToken(args[0], args[1])
|
||||
|
||||
user := mfxsdk.User{
|
||||
Email: args[0],
|
||||
Password: args[1],
|
||||
}
|
||||
token, err := sdk.CreateToken(user)
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(token)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewUsersCmd returns users command.
|
||||
func NewUsersCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "users",
|
||||
Short: "users create/token <email> <password>",
|
||||
Long: `Manages users in the system (create account or token)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
logUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdUsers {
|
||||
for i := range cmdUsers {
|
||||
cmd.AddCommand(&cmdUsers[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateUser - create user
|
||||
func CreateUser(user, pwd string) {
|
||||
msg := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, user, pwd)
|
||||
url := fmt.Sprintf("%s/users", serverAddr)
|
||||
resp, err := httpClient.Post(url, contentType, strings.NewReader(msg))
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
|
||||
// CreateToken - create user token
|
||||
func CreateToken(user, pwd string) {
|
||||
msg := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, user, pwd)
|
||||
url := fmt.Sprintf("%s/tokens", serverAddr)
|
||||
resp, err := httpClient.Post(url, contentType, strings.NewReader(msg))
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
|
||||
+15
-52
@@ -9,67 +9,30 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/fatih/color"
|
||||
"github.com/hokaccha/go-prettyjson"
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
var (
|
||||
// Limit query parameter
|
||||
Limit uint = 10
|
||||
// Offset query parameter
|
||||
Offset uint
|
||||
)
|
||||
|
||||
var Limit = 10
|
||||
var Offset = 0
|
||||
|
||||
func SendRequest(req *http.Request, token string, e error) {
|
||||
req.Header.Set("Authorization", token)
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
if e != nil {
|
||||
LogError(e)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
FormatResLog(resp, err)
|
||||
func flush(i interface{}) {
|
||||
fmt.Printf("%s", color.BlueString(spew.Sdump(i)))
|
||||
}
|
||||
|
||||
// FormatResLog - format http response
|
||||
func FormatResLog(resp *http.Response, err error) {
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf(color.CyanString("%s %s\nContent-Length: %v\n\n"),
|
||||
resp.Proto, resp.Status, resp.ContentLength)
|
||||
|
||||
if len(resp.Header.Get("Location")) != 0 {
|
||||
fmt.Printf(color.BlueString("Resource location: %s\n\n"),
|
||||
resp.Header.Get("Location"))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) != 0 {
|
||||
pj, err := prettyjson.Format([]byte(body))
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n\n", color.BlueString(string(body)))
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n\n", string(pj))
|
||||
}
|
||||
func logUsage(u string) {
|
||||
fmt.Printf(color.YellowString("Usage: %s\n"), u)
|
||||
}
|
||||
|
||||
func LogUsage(u string) {
|
||||
fmt.Printf(color.YellowString("Usage: %s\n\n"), u)
|
||||
func logError(err error) {
|
||||
fmt.Printf("%s\n", color.RedString(err.Error()))
|
||||
}
|
||||
|
||||
func LogError(err error) {
|
||||
fmt.Printf("%s\n\n", color.RedString(err.Error()))
|
||||
func logOK() {
|
||||
fmt.Printf("%s\n", color.GreenString("OK"))
|
||||
}
|
||||
|
||||
+10
-13
@@ -7,25 +7,22 @@
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewVersionCmd returns version command.
|
||||
func NewVersionCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get version of Mainflux Things Service",
|
||||
Long: `Mainflux server health checkt.`,
|
||||
Long: `Mainflux server health check`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Version()
|
||||
v, err := sdk.Version()
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(v)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Version - server health check
|
||||
func Version() {
|
||||
url := fmt.Sprintf("%s/version", serverAddr)
|
||||
FormatResLog(httpClient.Get(url))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/cassandra"
|
||||
@@ -30,11 +31,13 @@ import (
|
||||
const (
|
||||
sep = ","
|
||||
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
defThingsURL = "localhost:8181"
|
||||
|
||||
envLogLevel = "MF_CASSANDRA_READER_LOG_LEVEL"
|
||||
envPort = "MF_CASSANDRA_READER_PORT"
|
||||
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
|
||||
@@ -42,6 +45,7 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
logLevel string
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
@@ -51,7 +55,10 @@ type config struct {
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
|
||||
defer session.Close()
|
||||
@@ -72,12 +79,13 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Cassandra reader service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
cluster: mainflux.Env(envCluster, defCluster),
|
||||
keyspace: mainflux.Env(envKeyspace, defKeyspace),
|
||||
@@ -85,7 +93,7 @@ func loadConfig() config {
|
||||
}
|
||||
}
|
||||
|
||||
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
|
||||
func connectToCassandra(cluster, keyspace string, logger logger.Logger) *gocql.Session {
|
||||
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
|
||||
@@ -95,7 +103,7 @@ func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Sess
|
||||
return session
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
func connectToThings(url string, logger logger.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
@@ -105,7 +113,7 @@ func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(session *gocql.Session, logger log.Logger) readers.MessageRepository {
|
||||
func newService(session *gocql.Session, logger logger.Logger) readers.MessageRepository {
|
||||
repo := cassandra.New(session)
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
@@ -127,7 +135,7 @@ func newService(session *gocql.Session, logger log.Logger) readers.MessageReposi
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, errs chan error, logger log.Logger) {
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, errs chan error, logger logger.Logger) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Cassandra reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "cassandra-reader"))
|
||||
|
||||
@@ -9,6 +9,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -18,22 +19,25 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/cassandra"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
sep = ","
|
||||
queue = "cassandra-writer"
|
||||
sep = ","
|
||||
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_CASSANDRA_WRITER_LOG_LEVEL"
|
||||
envPort = "MF_CASSANDRA_WRITER_PORT"
|
||||
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
|
||||
@@ -41,6 +45,7 @@ const (
|
||||
|
||||
type config struct {
|
||||
natsURL string
|
||||
logLevel string
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
@@ -49,7 +54,10 @@ type config struct {
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
nc := connectToNATS(cfg.natsURL, logger)
|
||||
defer nc.Close()
|
||||
@@ -58,7 +66,7 @@ func main() {
|
||||
defer session.Close()
|
||||
|
||||
repo := newService(session, logger)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
if err := writers.Start(nc, repo, queue, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create Cassandra writer: %s", err))
|
||||
}
|
||||
|
||||
@@ -72,20 +80,21 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Cassandra writer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
natsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
cluster: mainflux.Env(envCluster, defCluster),
|
||||
keyspace: mainflux.Env(envKeyspace, defKeyspace),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToNATS(url string, logger log.Logger) *nats.Conn {
|
||||
func connectToNATS(url string, logger logger.Logger) *nats.Conn {
|
||||
nc, err := nats.Connect(url)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -95,7 +104,7 @@ func connectToNATS(url string, logger log.Logger) *nats.Conn {
|
||||
return nc
|
||||
}
|
||||
|
||||
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
|
||||
func connectToCassandra(cluster, keyspace string, logger logger.Logger) *gocql.Session {
|
||||
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
|
||||
@@ -105,7 +114,7 @@ func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Sess
|
||||
return session
|
||||
}
|
||||
|
||||
func newService(session *gocql.Session, logger log.Logger) writers.MessageRepository {
|
||||
func newService(session *gocql.Session, logger logger.Logger) writers.MessageRepository {
|
||||
repo := cassandra.New(session)
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(
|
||||
@@ -127,7 +136,7 @@ func newService(session *gocql.Session, logger log.Logger) writers.MessageReposi
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(port string, errs chan error, logger log.Logger) {
|
||||
func startHTTPServer(port string, errs chan error, logger logger.Logger) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Cassandra writer service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, cassandra.MakeHandler())
|
||||
|
||||
+72
-27
@@ -11,35 +11,28 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/mainflux/mainflux/cli"
|
||||
"github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
conf := struct {
|
||||
host string
|
||||
port int
|
||||
insecure bool
|
||||
}{
|
||||
"localhost",
|
||||
0,
|
||||
false,
|
||||
msgContentType := string(sdk.CTJSONSenML)
|
||||
sdkConf := sdk.Config{
|
||||
BaseURL: "http://localhost",
|
||||
UsersPrefix: "",
|
||||
ThingsPrefix: "",
|
||||
HTTPAdapterPrefix: "http",
|
||||
MsgContentType: sdk.ContentType(msgContentType),
|
||||
TLSVerification: false,
|
||||
}
|
||||
|
||||
// Root
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "mainflux-cli",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
var proto string
|
||||
|
||||
if conf.insecure {
|
||||
proto = "http"
|
||||
} else {
|
||||
proto = "https"
|
||||
cli.SetCerts()
|
||||
}
|
||||
|
||||
cli.SetServerAddr(proto, conf.host, conf.port)
|
||||
sdkConf.MsgContentType = sdk.ContentType(msgContentType)
|
||||
s := sdk.NewSDK(sdkConf)
|
||||
cli.SetSDK(s)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -59,17 +52,69 @@ func main() {
|
||||
|
||||
// Root Flags
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&conf.host, "host", "m", conf.host, "HTTP Host address")
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&conf.port, "port", "p", conf.port, "HTTP Host Port")
|
||||
&sdkConf.BaseURL,
|
||||
"mainflux-url",
|
||||
"m",
|
||||
sdkConf.BaseURL,
|
||||
"Mainflux host URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.UsersPrefix,
|
||||
"users-prefix",
|
||||
"u",
|
||||
sdkConf.UsersPrefix,
|
||||
"Mainflux users service prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.ThingsPrefix,
|
||||
"things-prefix",
|
||||
"t",
|
||||
sdkConf.ThingsPrefix,
|
||||
"Mainflux things service prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.HTTPAdapterPrefix,
|
||||
"http-prefix",
|
||||
"a",
|
||||
sdkConf.HTTPAdapterPrefix,
|
||||
"Mainflux http adapter prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&msgContentType,
|
||||
"content-type",
|
||||
"c",
|
||||
msgContentType,
|
||||
"Mainflux message content type",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&conf.insecure, "insecure", "i", false, "do not use TLS")
|
||||
&sdkConf.TLSVerification,
|
||||
"insecure",
|
||||
"i",
|
||||
sdkConf.TLSVerification,
|
||||
"Do not check for TLS cert",
|
||||
)
|
||||
|
||||
// Client and Channels Flags
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&cli.Limit, "limit", "l", 100, "limit query parameter")
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&cli.Offset, "offset", "o", 0, "offset query parameter")
|
||||
rootCmd.PersistentFlags().UintVarP(
|
||||
&cli.Limit,
|
||||
"limit",
|
||||
"l",
|
||||
100,
|
||||
"limit query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().UintVarP(
|
||||
&cli.Offset,
|
||||
"offset",
|
||||
"o",
|
||||
0,
|
||||
"offset query parameter",
|
||||
)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
+21
-7
@@ -9,6 +9,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -19,7 +20,7 @@ import (
|
||||
adapter "github.com/mainflux/mainflux/http"
|
||||
"github.com/mainflux/mainflux/http/api"
|
||||
"github.com/mainflux/mainflux/http/nats"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
@@ -28,9 +29,11 @@ import (
|
||||
|
||||
const (
|
||||
defPort string = "8180"
|
||||
defLogLevel string = "error"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defThingsURL string = "localhost:8181"
|
||||
envPort string = "MF_HTTP_ADAPTER_PORT"
|
||||
envLogLevel string = "MF_HTTP_ADAPTER_LOG_LEVEL"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envThingsURL string = "MF_THINGS_URL"
|
||||
)
|
||||
@@ -38,17 +41,18 @@ const (
|
||||
type config struct {
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
@@ -102,3 +106,13 @@ func main() {
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("HTTP adapter terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/influxdb"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
|
||||
const (
|
||||
defThingsURL = "localhost:8181"
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
@@ -29,6 +31,7 @@ const (
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envLogLevel = "MF_INFLUX_READER_LOG_LEVEL"
|
||||
envPort = "MF_INFLUX_READER_PORT"
|
||||
envDBName = "MF_INFLUX_READER_DB_NAME"
|
||||
envDBHost = "MF_INFLUX_READER_DB_HOST"
|
||||
@@ -39,6 +42,7 @@ const (
|
||||
|
||||
type config struct {
|
||||
ThingsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
@@ -49,8 +53,10 @@ type config struct {
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
conn := connectToThings(cfg.ThingsURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
@@ -85,6 +91,7 @@ func main() {
|
||||
func loadConfigs() (config, influxdata.HTTPConfig) {
|
||||
cfg := config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
@@ -102,7 +109,7 @@ func loadConfigs() (config, influxdata.HTTPConfig) {
|
||||
return cfg, clientCfg
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
func connectToThings(url string, logger logger.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
@@ -112,7 +119,7 @@ func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(client influxdata.Client, logger log.Logger) readers.MessageRepository {
|
||||
func newService(client influxdata.Client, logger logger.Logger) readers.MessageRepository {
|
||||
repo, _ := influxdb.New(client, "mainflux")
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
@@ -134,7 +141,7 @@ func newService(client influxdata.Client, logger log.Logger) readers.MessageRepo
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("InfluxDB reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "influxdb-reader"))
|
||||
|
||||
+68
-37
@@ -9,54 +9,69 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/influxdb"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL = nats.DefaultURL
|
||||
defPort = "8180"
|
||||
defPointName = "messages"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
queue = "influxdb-writer"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envPort = "MF_INFLUX_WRITER_PORT"
|
||||
envDBName = "MF_INFLUX_WRITER_DB_NAME"
|
||||
envDBHost = "MF_INFLUX_WRITER_DB_HOST"
|
||||
envDBPort = "MF_INFLUX_WRITER_DB_PORT"
|
||||
envDBUser = "MF_INFLUX_WRITER_DB_USER"
|
||||
envDBPass = "MF_INFLUX_WRITER_DB_PASS"
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defBatchSize = "5000"
|
||||
defBatchTimeout = "5"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_INFLUX_WRITER_LOG_LEVEL"
|
||||
envPort = "MF_INFLUX_WRITER_PORT"
|
||||
envBatchSize = "MF_INFLUX_WRITER_BATCH_SIZE"
|
||||
envBatchTimeout = "MF_INFLUX_WRITER_BATCH_TIMEOUT"
|
||||
envDBName = "MF_INFLUX_WRITER_DB_NAME"
|
||||
envDBHost = "MF_INFLUX_WRITER_DB_HOST"
|
||||
envDBPort = "MF_INFLUX_WRITER_DB_PORT"
|
||||
envDBUser = "MF_INFLUX_WRITER_DB_USER"
|
||||
envDBPass = "MF_INFLUX_WRITER_DB_PASS"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
BatchSize string
|
||||
BatchTimeout string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
nc, err := nats.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -71,7 +86,20 @@ func main() {
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
repo, err := influxdb.New(client, cfg.DBName)
|
||||
batchTimeout, err := strconv.Atoi(cfg.BatchTimeout)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Invalid value for batch timeout: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
batchSize, err := strconv.Atoi(cfg.BatchSize)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Invalid value of batch size: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
timeout := time.Duration(batchTimeout) * time.Second
|
||||
repo, err := influxdb.New(client, cfg.DBName, batchSize, timeout)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
|
||||
os.Exit(1)
|
||||
@@ -80,7 +108,7 @@ func main() {
|
||||
counter, latency := makeMetrics()
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(repo, counter, latency)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
if err := writers.Start(nc, repo, queue, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -100,13 +128,16 @@ func main() {
|
||||
|
||||
func loadConfigs() (config, influxdata.HTTPConfig) {
|
||||
cfg := config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
BatchSize: mainflux.Env(envBatchSize, defBatchSize),
|
||||
BatchTimeout: mainflux.Env(envBatchTimeout, defBatchTimeout),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
}
|
||||
|
||||
clientCfg := influxdata.HTTPConfig{
|
||||
@@ -136,8 +167,8 @@ func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
|
||||
return counter, latency
|
||||
}
|
||||
|
||||
func startHTTPService(port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPService(port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Influxdb writer service started, exposed port %s", p))
|
||||
logger.Info(fmt.Sprintf("InfluxDB writer service started, exposed port %s", p))
|
||||
errs <- http.ListenAndServe(p, influxdb.MakeHandler())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -17,7 +18,7 @@ import (
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/mongodb"
|
||||
@@ -29,12 +30,14 @@ import (
|
||||
|
||||
const (
|
||||
defThingsURL = "localhost:8181"
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envLogLevel = "MF_MONGO_READER_LOG_LEVEL"
|
||||
envPort = "MF_MONGO_READER_PORT"
|
||||
envDBName = "MF_MONGO_READER_DB_NAME"
|
||||
envDBHost = "MF_MONGO_READER_DB_HOST"
|
||||
@@ -43,6 +46,7 @@ const (
|
||||
|
||||
type config struct {
|
||||
thingsURL string
|
||||
logLevel string
|
||||
port string
|
||||
dbName string
|
||||
dbHost string
|
||||
@@ -51,8 +55,10 @@ type config struct {
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
conn := connectToThings(cfg.thingsURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
@@ -71,13 +77,14 @@ func main() {
|
||||
|
||||
go startHTTPServer(repo, tc, cfg.port, logger, errs)
|
||||
|
||||
err := <-errs
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("MongoDB reader service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfigs() config {
|
||||
return config{
|
||||
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
dbName: mainflux.Env(envDBName, defDBName),
|
||||
dbHost: mainflux.Env(envDBHost, defDBHost),
|
||||
@@ -85,7 +92,7 @@ func loadConfigs() config {
|
||||
}
|
||||
}
|
||||
|
||||
func connectToMongoDB(host, port, name string, logger log.Logger) *mongo.Database {
|
||||
func connectToMongoDB(host, port, name string, logger logger.Logger) *mongo.Database {
|
||||
client, err := mongo.Connect(context.Background(), fmt.Sprintf("mongodb://%s:%s", host, port), nil)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to database: %s", err))
|
||||
@@ -95,7 +102,7 @@ func connectToMongoDB(host, port, name string, logger log.Logger) *mongo.Databas
|
||||
return client.Database(name)
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
func connectToThings(url string, logger logger.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
@@ -105,7 +112,7 @@ func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(db *mongo.Database, logger log.Logger) readers.MessageRepository {
|
||||
func newService(db *mongo.Database, logger logger.Logger) readers.MessageRepository {
|
||||
repo := mongodb.New(db)
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
@@ -127,7 +134,7 @@ func newService(db *mongo.Database, logger log.Logger) readers.MessageRepository
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Mongo reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "cassandra-reader"))
|
||||
|
||||
+36
-27
@@ -10,6 +10,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -17,40 +18,47 @@ import (
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
mongodb "github.com/mainflux/mainflux/writers/mongodb"
|
||||
"github.com/mainflux/mainflux/writers/mongodb"
|
||||
"github.com/mongodb/mongo-go-driver/mongo"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL = nats.DefaultURL
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
queue = "mongodb-writer"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envPort = "MF_MONGO_WRITER_PORT"
|
||||
envDBName = "MF_MONGO_WRITER_DB_NAME"
|
||||
envDBHost = "MF_MONGO_WRITER_DB_HOST"
|
||||
envDBPort = "MF_MONGO_WRITER_DB_PORT"
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_MONGO_WRITER_LOG_LEVEL"
|
||||
envPort = "MF_MONGO_WRITER_PORT"
|
||||
envDBName = "MF_MONGO_WRITER_DB_NAME"
|
||||
envDBHost = "MF_MONGO_WRITER_DB_HOST"
|
||||
envDBPort = "MF_MONGO_WRITER_DB_PORT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
nc, err := nats.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -70,7 +78,7 @@ func main() {
|
||||
counter, latency := makeMetrics()
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(repo, counter, latency)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
if err := writers.Start(nc, repo, queue, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -90,11 +98,12 @@ func main() {
|
||||
|
||||
func loadConfigs() config {
|
||||
return config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +125,7 @@ func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
|
||||
return counter, latency
|
||||
}
|
||||
|
||||
func startHTTPService(port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPService(port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Mongodb writer service started, exposed port %s", p))
|
||||
errs <- http.ListenAndServe(p, mongodb.MakeHandler())
|
||||
|
||||
+24
-13
@@ -9,13 +9,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/normalizer"
|
||||
"github.com/mainflux/mainflux/normalizer/api"
|
||||
"github.com/mainflux/mainflux/normalizer/nats"
|
||||
@@ -26,25 +27,27 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defPort string = "8180"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envPort string = "MF_NORMALIZER_PORT"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defLogLevel string = "error"
|
||||
defPort string = "8180"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envLogLevel string = "MF_NORMALIZER_LOG_LEVEL"
|
||||
envPort string = "MF_NORMALIZER_PORT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -89,3 +92,11 @@ func main() {
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Normalizer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
}
|
||||
|
||||
+30
-22
@@ -10,6 +10,7 @@ package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/things"
|
||||
"github.com/mainflux/mainflux/things/api"
|
||||
grpcapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defLogLevel = "error"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
@@ -45,6 +47,7 @@ const (
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defUsersURL = "localhost:8181"
|
||||
envLogLevel = "MF_THINGS_LOG_LEVEL"
|
||||
envDBHost = "MF_THINGS_DB_HOST"
|
||||
envDBPort = "MF_THINGS_DB_PORT"
|
||||
envDBUser = "MF_THINGS_DB_USER"
|
||||
@@ -59,6 +62,7 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
@@ -66,18 +70,20 @@ type config struct {
|
||||
DBName string
|
||||
CacheURL string
|
||||
CachePass string
|
||||
CacheDB int
|
||||
CacheDB string
|
||||
HTTPPort string
|
||||
GRPCPort string
|
||||
UsersURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger := log.New(os.Stdout)
|
||||
cfg := loadConfig()
|
||||
|
||||
cfg := loadConfig(logger)
|
||||
|
||||
cache := connectToCache(cfg.CacheURL, cfg.CachePass, cfg.CacheDB)
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
cache := connectToCache(cfg.CacheURL, cfg.CachePass, cfg.CacheDB, logger)
|
||||
|
||||
db := connectToDB(cfg, logger)
|
||||
defer db.Close()
|
||||
@@ -97,18 +103,13 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Things service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig(logger log.Logger) config {
|
||||
db, err := strconv.Atoi(mainflux.Env(envCacheDB, defCacheDB))
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to cache: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
@@ -116,22 +117,29 @@ func loadConfig(logger log.Logger) config {
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
CacheURL: mainflux.Env(envCacheURL, defCacheURL),
|
||||
CachePass: mainflux.Env(envCachePass, defCachePass),
|
||||
CacheDB: db,
|
||||
CacheDB: mainflux.Env(envCacheDB, defCacheDB),
|
||||
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToCache(cacheURL, cachePass string, cacheDB int) *redis.Client {
|
||||
func connectToCache(cacheURL, cachePass string, cacheDB string, logger logger.Logger) *redis.Client {
|
||||
|
||||
db, err := strconv.Atoi(cacheDB)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to cache: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return redis.NewClient(&redis.Options{
|
||||
Addr: cacheURL,
|
||||
Password: cachePass,
|
||||
DB: cacheDB,
|
||||
DB: db,
|
||||
})
|
||||
}
|
||||
|
||||
func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
func connectToDB(cfg config, logger logger.Logger) *sql.DB {
|
||||
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
@@ -140,7 +148,7 @@ func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func connectToUsersService(usersAddr string, logger log.Logger) *grpc.ClientConn {
|
||||
func connectToUsersService(usersAddr string, logger logger.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(usersAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
@@ -149,7 +157,7 @@ func connectToUsersService(usersAddr string, logger log.Logger) *grpc.ClientConn
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(conn *grpc.ClientConn, db *sql.DB, client *redis.Client, logger log.Logger) things.Service {
|
||||
func newService(conn *grpc.ClientConn, db *sql.DB, client *redis.Client, logger logger.Logger) things.Service {
|
||||
users := usersapi.NewClient(conn)
|
||||
thingsRepo := postgres.NewThingRepository(db, logger)
|
||||
channelsRepo := postgres.NewChannelRepository(db, logger)
|
||||
@@ -177,13 +185,13 @@ func newService(conn *grpc.ClientConn, db *sql.DB, client *redis.Client, logger
|
||||
return svc
|
||||
}
|
||||
|
||||
func startHTTPServer(svc things.Service, port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPServer(svc things.Service, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Things service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc))
|
||||
}
|
||||
|
||||
func startGRPCServer(svc things.Service, port string, logger log.Logger, errs chan error) {
|
||||
func startGRPCServer(svc things.Service, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
|
||||
+15
-8
@@ -10,6 +10,7 @@ package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/api"
|
||||
grpcapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defLogLevel = "error"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
@@ -39,6 +41,7 @@ const (
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defSecret = "users"
|
||||
envLogLevel = "MF_USERS_LOG_LEVEL"
|
||||
envDBHost = "MF_USERS_DB_HOST"
|
||||
envDBPort = "MF_USERS_DB_PORT"
|
||||
envDBUser = "MF_USERS_DB_USER"
|
||||
@@ -50,6 +53,7 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
@@ -63,8 +67,10 @@ type config struct {
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
db := connectToDB(cfg, logger)
|
||||
defer db.Close()
|
||||
|
||||
@@ -80,12 +86,13 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Users service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
@@ -97,7 +104,7 @@ func loadConfig() config {
|
||||
}
|
||||
}
|
||||
|
||||
func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
func connectToDB(cfg config, logger logger.Logger) *sql.DB {
|
||||
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
@@ -106,7 +113,7 @@ func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func newService(db *sql.DB, secret string, logger log.Logger) users.Service {
|
||||
func newService(db *sql.DB, secret string, logger logger.Logger) users.Service {
|
||||
repo := postgres.New(db)
|
||||
hasher := bcrypt.New()
|
||||
idp := jwt.New(secret)
|
||||
@@ -131,13 +138,13 @@ func newService(db *sql.DB, secret string, logger log.Logger) users.Service {
|
||||
return svc
|
||||
}
|
||||
|
||||
func startHTTPServer(svc users.Service, port string, logger log.Logger, errs chan error) {
|
||||
func startHTTPServer(svc users.Service, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Users HTTP service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, logger))
|
||||
}
|
||||
|
||||
func startGRPCServer(svc users.Service, port string, logger log.Logger, errs chan error) {
|
||||
func startGRPCServer(svc users.Service, port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
|
||||
+18
-8
@@ -9,6 +9,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
adapter "github.com/mainflux/mainflux/ws"
|
||||
"github.com/mainflux/mainflux/ws/api"
|
||||
@@ -28,9 +29,11 @@ import (
|
||||
|
||||
const (
|
||||
defPort = "8180"
|
||||
defLogLevel = "error"
|
||||
defNatsURL = broker.DefaultURL
|
||||
defThingsURL = "localhost:8181"
|
||||
envPort = "MF_WS_ADAPTER_PORT"
|
||||
envLogLevel = "MF_WS_ADAPTER_LOG_LEVEL"
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
)
|
||||
@@ -38,18 +41,17 @@ const (
|
||||
type config struct {
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -101,3 +103,11 @@ func main() {
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("WebSocket adapter terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM scratch
|
||||
ARG SVC_NAME
|
||||
COPY mainflux-$SVC_NAME /exe
|
||||
ENTRYPOINT ["/exe"]
|
||||
@@ -35,6 +35,8 @@ services:
|
||||
environment:
|
||||
MF_NATS_URL: nats://nats:4222
|
||||
MF_INFLUX_WRITER_PORT: 8900
|
||||
MF_INFLUX_WRITER_BATCH_SIZE: 5000
|
||||
MF_INFLUX_WRITER_BATCH_TIMEOUT: 5
|
||||
MF_INFLUX_WRITER_DB_NAME: mainflux
|
||||
MF_INFLUX_WRITER_DB_HOST: mainflux-influxdb
|
||||
MF_INFLUX_WRITER_DB_PORT: 8086
|
||||
|
||||
@@ -16,8 +16,6 @@ services:
|
||||
mongodb-reader:
|
||||
image: mainflux/mongodb-reader:latest
|
||||
container_name: mainflux-mongodb-reader
|
||||
expose:
|
||||
- 8901
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_THINGS_URL: things:8183
|
||||
|
||||
@@ -30,7 +30,7 @@ services:
|
||||
- mainflux-base-net
|
||||
|
||||
nats:
|
||||
image: nats:1.1.0
|
||||
image: nats:1.3.0
|
||||
container_name: mainflux-nats
|
||||
restart: on-failure
|
||||
networks:
|
||||
|
||||
+1
-9
@@ -58,18 +58,10 @@ http {
|
||||
# Virtual Host Configs
|
||||
##
|
||||
|
||||
# HTTP
|
||||
# HTTPS
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name localhost;
|
||||
access_log off;
|
||||
error_log off;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS
|
||||
server {
|
||||
# SSL configuration
|
||||
#
|
||||
listen 443 ssl http2 default_server;
|
||||
|
||||
+40
-28
@@ -1,6 +1,6 @@
|
||||
## Getting Mainflux
|
||||
|
||||
Mainflux can be fetched from official [Mainflux GitHub repository](https://github.com/Mainflux/mainflux):
|
||||
Mainflux can be fetched from the official [Mainflux GitHub repository](https://github.com/Mainflux/mainflux):
|
||||
|
||||
```
|
||||
go get github.com/mainflux/mainflux
|
||||
@@ -17,7 +17,7 @@ Go Protobuf uses C bindings, so you will need to install [C++ protobuf](https://
|
||||
|
||||
### Build All Services
|
||||
|
||||
Use `GNU Make` tool to build all mainflux services:
|
||||
Use `GNU Make` tool to build all Mainflux services:
|
||||
|
||||
```
|
||||
make
|
||||
@@ -25,10 +25,10 @@ make
|
||||
|
||||
Build artefacts will be put in the `build` directory.
|
||||
|
||||
> N.B. All Mainflux services are built as a statically linked binaries. This way they can be portable (transfered to any platform just by placing them there and running them) as they contain all needed libraries and do not relay on system shared libs. This helps creating [FROM scratch](https://hub.docker.com/_/scratch/) dockers.
|
||||
> N.B. All Mainflux services are built as a statically linked binaries. This way they can be portable (transferred to any platform just by placing them there and running them) as they contain all needed libraries and do not relay on shared system libraries. This helps creating [FROM scratch](https://hub.docker.com/_/scratch/) dockers.
|
||||
|
||||
### Build Individual Microservice
|
||||
Individual microservices can be built with command:
|
||||
Individual microservices can be built with:
|
||||
|
||||
```
|
||||
make <microservice_name>
|
||||
@@ -40,7 +40,7 @@ For example:
|
||||
make http
|
||||
```
|
||||
|
||||
will build HTTP Adapter microservice.
|
||||
will build the HTTP Adapter microservice.
|
||||
|
||||
### Building Dockers
|
||||
|
||||
@@ -50,7 +50,7 @@ Dockers can be built with:
|
||||
make dockers
|
||||
```
|
||||
|
||||
or individually with
|
||||
or individually with:
|
||||
|
||||
```
|
||||
make docker_<microservice_name>
|
||||
@@ -62,14 +62,28 @@ For example:
|
||||
make docker_http
|
||||
```
|
||||
|
||||
> N.B. Mainflux creates `FROM scratch` docker containers which as compact and small in size.
|
||||
> N.B. Mainflux creates `FROM scratch` docker containers which are compact and small in size.
|
||||
|
||||
> N.B. The `things-db` and `users-db` containers are built from a vanilla PostgreSQL Docker image downloaded from Docker Hub which does not persist the data when these containers are rebuilt. Thus, __rebuilding of all Docker containers with `make dockers` or rebuilding the `things-db` and `users-db` containers separately with `make docker_things-db` and `make docker_users-db` respectively, will cause data loss. All your users, things, channels and connections between them will be lost!__ As we use this setup only for development, we don't guarantee any permanent data persistence. If you need to retain the data between the container rebuilds you can attach volume to the `things-db` and `users-db` containers. Check the official docs on how to use volumes [here](https
|
||||
://docs.docker.com/storage/volumes/) and [here](https://docs.docker.com/compose/compose-file/#volumes).
|
||||
> N.B. The `things-db` and `users-db` containers are built from a vanilla PostgreSQL docker image downloaded from docker hub which does not persist the data when these containers are rebuilt. Thus, __rebuilding of all docker containers with `make dockers` or rebuilding the `things-db` and `users-db` containers separately with `make docker_things-db` and `make docker_users-db` respectively, will cause data loss. All your users, things, channels and connections between them will be lost!__ As we use this setup only for development, we don't guarantee any permanent data persistence. If you need to retain the data between the container rebuilds you can attach volume to the `things-db` and `users-db` containers. Check the official docs on how to use volumes [here](https://docs.docker.com/storage/volumes/) and [here](https://docs.docker.com/compose/compose-file/#volumes).
|
||||
|
||||
#### Building Docker images for development
|
||||
|
||||
In order to speed up build process, you can use commands such as:
|
||||
|
||||
```bash
|
||||
make dockers_dev
|
||||
```
|
||||
|
||||
or individually with
|
||||
|
||||
```bash
|
||||
make docker_dev_<microservice_name>
|
||||
```
|
||||
|
||||
Commands `make dockers` and `make dockers_dev` are similar. The main difference is that building images in the development mode is done on the local machine, rather than an intermediate image, which makes building images much faster. Before running this command, corresponding binary needs to be built in order to make changes visible. This can be done using `make` or `make <service_name>` command. Commands `make dockers_dev` and `make docker_dev_<service_name>` should be used only for development to speed up the process of image building. **For deployment images, commands from section above should be used.**
|
||||
|
||||
### MQTT Microservice
|
||||
MQTT Microservice in Mainflux is special, as it is currently the only microservice written in NodeJS. It is not compiled,
|
||||
but node modules need to be downloaded in order to start the service:
|
||||
The MQTT Microservice in Mainflux is special, as it is currently the only microservice written in NodeJS. It is not compiled, but node modules need to be downloaded in order to start the service:
|
||||
|
||||
```
|
||||
cd mqtt
|
||||
@@ -82,7 +96,7 @@ Note that there is a shorthand for doing these commands with `make` tool:
|
||||
make mqtt
|
||||
```
|
||||
|
||||
After that MQTT Adapter can be started from top directory (as it needs to find `*.proto` files) with:
|
||||
After that, the MQTT Adapter can be started from top directory (as it needs to find `*.proto` files) with:
|
||||
```
|
||||
node mqtt/mqtt.js
|
||||
```
|
||||
@@ -102,13 +116,11 @@ A shorthand to do this via `make` tool is:
|
||||
make proto
|
||||
```
|
||||
|
||||
> N.B. This must be done one time in the beginning in order to generate protobuf Go structures needed for the build.
|
||||
> N.B. This must be done once at the beginning in order to generate protobuf Go structures needed for the build.
|
||||
|
||||
### Cross-compiling for ARM
|
||||
Mainflux can be compiled for ARM platform and run on Raspberry Pi or other similar IoT gateways.
|
||||
|
||||
Following the instructions [here](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) or [here](https://www.alexruf.net/golang/arm/raspberrypi/2016/01/16/cross-compile-with-go-1-5-for-raspberry-pi.html) as well as information
|
||||
found [here](https://github.com/golang/go/wiki/GoArm), environment variables `GOARCH=arm` and `GOARM=7` must be set for the compilation.
|
||||
Mainflux can be compiled for ARM platform and run on Raspberry Pi or other similar IoT gateways, by following the instructions [here](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) or [here](https://www.alexruf.net/golang/arm/raspberrypi/2016/01/16/cross-compile-with-go-1-5-for-raspberry-pi.html) as well as information
|
||||
found [here](https://github.com/golang/go/wiki/GoArm). The environment variables `GOARCH=arm` and `GOARM=7` must be set for the compilation.
|
||||
|
||||
Cross-compilation for ARM with Mainflux make:
|
||||
|
||||
@@ -117,11 +129,11 @@ GOOS=linux GOARCH=arm GOARM=7 make
|
||||
```
|
||||
|
||||
## Running tests
|
||||
To run all of the test you can execute:
|
||||
To run all of the tests you can execute:
|
||||
```
|
||||
make test
|
||||
```
|
||||
Dockertest is used for the test, so to run the tests you will need the Docker deamon/service running.
|
||||
Dockertest is used for the tests, so to run them, you will need the Docker daemon/service running.
|
||||
|
||||
## Installing
|
||||
Installing Go binaries is simple: just move them from `build` to `$GOBIN` (do not fortget to add `$GOBIN` to your `$PATH`).
|
||||
@@ -132,14 +144,14 @@ You can execute:
|
||||
make install
|
||||
```
|
||||
|
||||
which will do this copy of binaries.
|
||||
which will do this copying of the binaries.
|
||||
|
||||
> N.B. Only Go binaries will be installed this way. MQTT adapter is NodeJS script and will stay in `mqtt` dir.
|
||||
> N.B. Only Go binaries will be installed this way. The MQTT adapter is a NodeJS script and will stay in the `mqtt` dir.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Prerequisites
|
||||
Mainflux depends on several infrastructureal services, notably [NATS](https://www.nats.io/) broker and [PostgreSQL](https://www.postgresql.org/) database.
|
||||
Mainflux depends on several infrastructural services, notably [NATS](https://www.nats.io/) broker and [PostgreSQL](https://www.postgresql.org/) database.
|
||||
|
||||
#### NATS
|
||||
Mainflux uses NATS as it's central message bus. For development purposes (when not run via Docker), it expects that NATS is installed on the local system.
|
||||
@@ -147,10 +159,10 @@ Mainflux uses NATS as it's central message bus. For development purposes (when n
|
||||
To do this execute:
|
||||
|
||||
```
|
||||
go get github.com/nats-io/go-nats
|
||||
go get github.com/nats-io/gnatsd
|
||||
```
|
||||
|
||||
This will install `gnatsd` binary that can be simply run by invoking
|
||||
This will install `gnatsd` binary that can be simply run by executing:
|
||||
|
||||
```
|
||||
gnatsd
|
||||
@@ -158,9 +170,9 @@ gnatsd
|
||||
|
||||
#### PostgreSQL
|
||||
Mainflux uses PostgreSQL to store metadata (`users`, `things` and `channels` entities alongside with authorization tokens).
|
||||
It expects that PostgreSQL DB is installed, setu-up and running on the local system.
|
||||
It expects that PostgreSQL DB is installed, set up and running on the local system.
|
||||
|
||||
Inflormation how to set-up (prepare) PostgreSQL database can be found [here](https://support.rackspace.com/how-to/postgresql-creating-and-dropping-roles/),
|
||||
Information how to set-up (prepare) PostgreSQL database can be found [here](https://support.rackspace.com/how-to/postgresql-creating-and-dropping-roles/),
|
||||
and it is done by executing following commands:
|
||||
|
||||
```
|
||||
@@ -176,9 +188,9 @@ postgres=# ALTER USER mainflux WITH LOGIN ENCRYPTED PASSWORD 'mainflux';
|
||||
```
|
||||
|
||||
### Mainflux Services
|
||||
Running of the Mainflux microservices can be tricky, as there is a lot of them and each demand config in the form of environment variables.
|
||||
Running of the Mainflux microservices can be tricky, as there is a lot of them and each demand configuration in the form of environment variables.
|
||||
|
||||
Whole system (set of microservices) can be run with one command:
|
||||
The whole system (set of microservices) can be run with one command:
|
||||
|
||||
```
|
||||
make run
|
||||
|
||||
@@ -327,3 +327,134 @@ mosquitto_sub -u <thing_id> -P <thing_key> -t channels/<channel_id>/messages -h
|
||||
|
||||
If you are using TLS to secure MQTT connection, add `--cafile docker/ssl/certs/ca.crt`
|
||||
to every command.
|
||||
|
||||
## Add-ons
|
||||
|
||||
The `<project_root>/docker` folder contains an `addons` directory. This directory is used for various services that are not core to the Mainflux platform but could be used for providing additional features.
|
||||
|
||||
In order to run these services, core services, as well as the network from the core composition, should be already running.
|
||||
|
||||
### Writers
|
||||
|
||||
Writers provide an implementation of various `message writers`. Message writers are services that consume normalized (in `SenML` format) Mainflux messages and store them in specific data store.
|
||||
|
||||
#### InfluxDB, InfluxDB-writer and Grafana
|
||||
|
||||
From the project root execute the following command:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
|
||||
```
|
||||
This will install and start:
|
||||
|
||||
- [InfluxDB](https://docs.influxdata.com/influxdb) - time series database
|
||||
- InfluxDB writer - message repository implementation for InfluxDB
|
||||
- [Grafana](https://grafana.com) - tool for database exploration and data visualization and analytics
|
||||
|
||||
Those new services will take some additional ports:
|
||||
|
||||
- 8086 by InfluxDB
|
||||
- 8900 by InfluxDB writer service
|
||||
- 3001 by Grafana
|
||||
|
||||
To access Grafana, navigate to `http://localhost:3001` and login with: `admin`, password: `admin`
|
||||
|
||||
#### Cassandra and Cassandra-writer
|
||||
|
||||
```bash
|
||||
./docker/addons/cassandra-writer/init.sh
|
||||
```
|
||||
_Please note that Cassandra may not be suitable for your testing enviroment because it has high system requirements._
|
||||
|
||||
#### MongoDB and MongoDB-writer
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/addons/mongodb-writer/docker-compose.yml up -d
|
||||
```
|
||||
MongoDB default port (27017) is exposed, so you can use various tools for database inspection and data visualization.
|
||||
|
||||
### Readers
|
||||
|
||||
Readers provide an implementation of various `message readers`.
|
||||
Message readers are services that consume normalized (in `SenML` format) Mainflux messages from data storage and opens HTTP API for message consumption.
|
||||
Installing corresponding writer before reader is implied.
|
||||
|
||||
|
||||
#### InfluxDB-reader
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/addons/influxdb-reader/docker-compose.yml up -d
|
||||
```
|
||||
Service exposes [HTTP API](https://github.com/mainflux/mainflux/blob/master/readers/swagger.yml) for fetching messages on port 8905
|
||||
|
||||
|
||||
To read sent messages on channel with id `channel_id` you should send `GET` request to `/channels/<channel_id>/messages` with thing access token in `Authorization` header. That thing must be connected to channel with `channel_id`
|
||||
|
||||
```
|
||||
curl -s -S -i -H "Authorization: <thing_token>" http://localhost:8905/channels/<channel_id>/messages
|
||||
```
|
||||
|
||||
Response should look like this:
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Tue, 18 Sep 2018 18:56:19 GMT
|
||||
Content-Length: 228
|
||||
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"Channel": 1,
|
||||
"Publisher": 2,
|
||||
"Protocol": "mqtt",
|
||||
"Name": "name:voltage",
|
||||
"Unit": "V",
|
||||
"Value": 5.6,
|
||||
"Time": 48.56
|
||||
},
|
||||
{
|
||||
"Channel": 1,
|
||||
"Publisher": 2,
|
||||
"Protocol": "mqtt",
|
||||
"Name": "name:temperature",
|
||||
"Unit": "C",
|
||||
"Value": 24.3,
|
||||
"Time": 48.56
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Note that you will receive only those messages that were sent by authorization token's owner.
|
||||
You can specify `offset` and `limit` parameters in order to fetch specific group of messages. In that case, your request should look like:
|
||||
|
||||
```
|
||||
curl -s -S -i -H "Authorization: <thing_token>" http://localhost:8905/channels/<channel_id>/messages?offset=0&limit=5
|
||||
```
|
||||
If you don't provide them, default values will be used instead: 0 for `offset`, and 10 for `limit`.
|
||||
|
||||
#### Cassandra-reader
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/addons/cassandra-reader/docker-compose.yml up -d
|
||||
```
|
||||
Service exposes [HTTP API](https://github.com/mainflux/mainflux/blob/master/readers/swagger.yml) for fetching messages on port 8903
|
||||
|
||||
Aside from port, reading request is same as for other readers:
|
||||
|
||||
```
|
||||
curl -s -S -i -H "Authorization: <thing_token>" http://localhost:8903/channels/<channel_id>/messages
|
||||
```
|
||||
|
||||
#### MongoDB-reader
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/addons/mongodb-reader/docker-compose.yml up -d
|
||||
```
|
||||
Service exposes [HTTP API](https://github.com/mainflux/mainflux/blob/master/readers/swagger.yml) for fetching messages on port 8904
|
||||
|
||||
Aside from port, reading request is same as for other readers:
|
||||
|
||||
```
|
||||
curl -s -S -i -H "Authorization: <thing_token>" http://localhost:8904/channels/<channel_id>/messages
|
||||
```
|
||||
|
||||
+8
-6
@@ -8,11 +8,12 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------------------|---------------------|-----------------------|
|
||||
| MF_HTTP_ADAPTER_PORT | Service HTTP port | 8180 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
| Variable | Description | Default |
|
||||
|-----------------------------|--------------------------------|-----------------------|
|
||||
| MF_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter | error |
|
||||
| MF_HTTP_ADAPTER_PORT | Service HTTP port | 8180 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -30,6 +31,7 @@ services:
|
||||
environment:
|
||||
MF_THINGS_URL: [Things service URL]
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_HTTP_ADAPTER_LOG_LEVEL: [HTTP Adapter Log Level]
|
||||
MF_HTTP_ADAPTER_PORT: [Service HTTP port]
|
||||
```
|
||||
|
||||
@@ -48,7 +50,7 @@ make http
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_HTTP_ADAPTER_PORT=[Service HTTP port] $GOBIN/mainflux-http
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_HTTP_ADAPTER_LOG_LEVEL=[HTTP Adapter Log Level] MF_HTTP_ADAPTER_PORT=[Service HTTP port] $GOBIN/mainflux-http
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -12,8 +12,8 @@ spec:
|
||||
component: influxdb-writer
|
||||
spec:
|
||||
containers:
|
||||
- name: mainflux-influxdb
|
||||
image: mainflux/influxdb:latest
|
||||
- name: mainflux-influxdb-writer
|
||||
image: mainflux/influxdb-writer:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8900
|
||||
@@ -22,6 +22,10 @@ spec:
|
||||
value: "nats://nats:4222"
|
||||
- name: MF_INFLUX_WRITER_PORT
|
||||
value: "8900"
|
||||
- name: MF_INFLUX_WRITER_BATCH_SIZE
|
||||
value: "5000"
|
||||
- name: MF_INFLUX_WRITER_BATCH_TIMEOUT
|
||||
value: "5"
|
||||
- name: MF_INFLUX_WRITER_DB_NAME
|
||||
value: "mainflux"
|
||||
- name: MF_INFLUX_WRITER_DB_HOST
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Error level is used when logging errors.
|
||||
Error Level = iota + 1
|
||||
@@ -14,8 +19,12 @@ const (
|
||||
Warn
|
||||
// Info level is used when logging info data.
|
||||
Info
|
||||
// Debug level is used when logging debugging info.
|
||||
Debug
|
||||
)
|
||||
|
||||
var ErrInvalidLogLevel = errors.New("unrecognized log level")
|
||||
|
||||
// Level represents severity level while logging.
|
||||
type Level int
|
||||
|
||||
@@ -23,8 +32,29 @@ var levels = map[Level]string{
|
||||
Error: "error",
|
||||
Warn: "warn",
|
||||
Info: "info",
|
||||
Debug: "debug",
|
||||
}
|
||||
|
||||
func (lvl Level) String() string {
|
||||
return levels[lvl]
|
||||
}
|
||||
|
||||
func (lvl Level) isAllowed(logLevel Level) bool {
|
||||
return lvl <= logLevel
|
||||
}
|
||||
|
||||
func (lvl *Level) UnmarshalText(text string) error {
|
||||
switch string(strings.ToLower(text)) {
|
||||
case "debug":
|
||||
*lvl = Debug
|
||||
case "info":
|
||||
*lvl = Info
|
||||
case "warn":
|
||||
*lvl = Warn
|
||||
case "error":
|
||||
*lvl = Error
|
||||
default:
|
||||
return ErrInvalidLogLevel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalText(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
output Level
|
||||
err error
|
||||
}{
|
||||
"select log level Not_A_Level": {"Not_A_Level", 0, ErrInvalidLogLevel},
|
||||
"select log level Bad_Input": {"Bad_Input", 0, ErrInvalidLogLevel},
|
||||
|
||||
"select log level debug": {"debug", Debug, nil},
|
||||
"select log level DEBUG": {"DEBUG", Debug, nil},
|
||||
"select log level info": {"info", Info, nil},
|
||||
"select log level INFO": {"INFO", Info, nil},
|
||||
"select log level warn": {"warn", Warn, nil},
|
||||
"select log level WARN": {"WARN", Warn, nil},
|
||||
"select log level Error": {"Error", Error, nil},
|
||||
"select log level ERROR": {"ERROR", Error, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
var logLevel Level
|
||||
err := logLevel.UnmarshalText(tc.input)
|
||||
assert.Equal(t, tc.output, logLevel, fmt.Sprintf("%s: expected %s got %d", desc, tc.output, logLevel))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %d", desc, tc.err, err))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLevelIsAllowed(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
requestedLevel Level
|
||||
allowedLevel Level
|
||||
output bool
|
||||
}{
|
||||
"log debug when level debug": {Debug, Debug, true},
|
||||
"log info when level debug": {Info, Debug, true},
|
||||
"log warn when level debug": {Warn, Debug, true},
|
||||
"log error when level debug": {Error, Debug, true},
|
||||
"log warn when level info": {Warn, Info, true},
|
||||
"log error when level warn": {Error, Warn, true},
|
||||
"log error when level error": {Error, Error, true},
|
||||
|
||||
"log debug when level error": {Debug, Error, false},
|
||||
"log info when level error": {Info, Error, false},
|
||||
"log warn when level error": {Warn, Error, false},
|
||||
"log debug when level warn": {Debug, Warn, false},
|
||||
"log info when level warn": {Info, Warn, false},
|
||||
"log debug when level info": {Debug, Info, false},
|
||||
}
|
||||
for desc, tc := range cases {
|
||||
result := tc.requestedLevel.isAllowed(tc.allowedLevel)
|
||||
assert.Equal(t, tc.output, result, fmt.Sprintf("%s: expected %t got %t", desc, tc.output, result))
|
||||
}
|
||||
}
|
||||
+28
-7
@@ -8,13 +8,16 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"fmt"
|
||||
"github.com/go-kit/kit/log"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger specifies logging API.
|
||||
type Logger interface {
|
||||
// Debug logs any object in JSON format on debug level.
|
||||
Debug(string)
|
||||
// Info logs any object in JSON format on info level.
|
||||
Info(string)
|
||||
// Warn logs any object in JSON format on warning level.
|
||||
@@ -27,23 +30,41 @@ var _ Logger = (*logger)(nil)
|
||||
|
||||
type logger struct {
|
||||
kitLogger log.Logger
|
||||
level Level
|
||||
}
|
||||
|
||||
// New returns wrapped go kit logger.
|
||||
func New(out io.Writer) Logger {
|
||||
func New(out io.Writer, levelText string) (Logger, error) {
|
||||
var level Level
|
||||
err := level.UnmarshalText(levelText)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`{"level":"error","message":"%s: %s","ts":"%s"}`, err, levelText, time.RFC3339Nano)
|
||||
}
|
||||
l := log.NewJSONLogger(log.NewSyncWriter(out))
|
||||
l = log.With(l, "ts", log.DefaultTimestampUTC)
|
||||
return &logger{l}
|
||||
return &logger{l, level}, err
|
||||
}
|
||||
|
||||
func (l logger) Debug(msg string) {
|
||||
if Debug.isAllowed(l.level) {
|
||||
l.kitLogger.Log("level", Debug.String(), "message", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l logger) Info(msg string) {
|
||||
l.kitLogger.Log("level", Info.String(), "message", msg)
|
||||
if Info.isAllowed(l.level) {
|
||||
l.kitLogger.Log("level", Info.String(), "message", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l logger) Warn(msg string) {
|
||||
l.kitLogger.Log("level", Warn.String(), "message", msg)
|
||||
if Warn.isAllowed(l.level) {
|
||||
l.kitLogger.Log("level", Warn.String(), "message", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l logger) Error(msg string) {
|
||||
l.kitLogger.Log("level", Error.String(), "message", msg)
|
||||
if Error.isAllowed(l.level) {
|
||||
l.kitLogger.Log("level", Error.String(), "message", msg)
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -18,6 +18,10 @@ import (
|
||||
)
|
||||
|
||||
var _ io.Writer = (*mockWriter)(nil)
|
||||
var writer mockWriter
|
||||
var logger log.Logger
|
||||
var err error
|
||||
var output logMsg
|
||||
|
||||
type mockWriter struct {
|
||||
value []byte
|
||||
@@ -39,42 +43,68 @@ type logMsg struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
func TestDebug(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
output logMsg
|
||||
input string
|
||||
logLevel string
|
||||
output logMsg
|
||||
}{
|
||||
"info log ordinary string": {"input_string", logMsg{log.Info.String(), "input_string"}},
|
||||
"info log empty string": {"", logMsg{log.Info.String(), ""}},
|
||||
"debug log ordinary string": {"input_string", log.Debug.String(), logMsg{log.Debug.String(), "input_string"}},
|
||||
"debug log empty string": {"", log.Debug.String(), logMsg{log.Debug.String(), ""}},
|
||||
"debug ordinary string lvl not allowed": {"input_string", log.Info.String(), logMsg{"", ""}},
|
||||
"debug empty string lvl not allowed": {"", log.Info.String(), logMsg{"", ""}},
|
||||
}
|
||||
|
||||
writer := mockWriter{}
|
||||
logger := log.New(&writer)
|
||||
for desc, tc := range cases {
|
||||
writer = mockWriter{}
|
||||
logger, err = log.New(&writer, tc.logLevel)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", desc, err))
|
||||
logger.Debug(tc.input)
|
||||
output, err = writer.Read()
|
||||
assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", desc, tc.output, output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
logLevel string
|
||||
output logMsg
|
||||
}{
|
||||
"info log ordinary string": {"input_string", log.Info.String(), logMsg{log.Info.String(), "input_string"}},
|
||||
"info log empty string": {"", log.Info.String(), logMsg{log.Info.String(), ""}},
|
||||
"info ordinary string lvl not allowed": {"input_string", log.Warn.String(), logMsg{"", ""}},
|
||||
"info empty string lvl not allowed": {"", log.Warn.String(), logMsg{"", ""}},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
logger.Info(tc.input)
|
||||
output, err := writer.Read()
|
||||
writer = mockWriter{}
|
||||
logger, err = log.New(&writer, tc.logLevel)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", desc, err))
|
||||
logger.Info(tc.input)
|
||||
output, err = writer.Read()
|
||||
assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", desc, tc.output, output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
output logMsg
|
||||
input string
|
||||
logLevel string
|
||||
output logMsg
|
||||
}{
|
||||
"warn log ordinary string": {"input_string", logMsg{log.Warn.String(), "input_string"}},
|
||||
"warn log empty string": {"", logMsg{log.Warn.String(), ""}},
|
||||
"warn log ordinary string": {"input_string", log.Warn.String(), logMsg{log.Warn.String(), "input_string"}},
|
||||
"warn log empty string": {"", log.Warn.String(), logMsg{log.Warn.String(), ""}},
|
||||
"warn ordinary string lvl not allowed": {"input_string", log.Error.String(), logMsg{"", ""}},
|
||||
"warn empty string lvl not allowed": {"", log.Error.String(), logMsg{"", ""}},
|
||||
}
|
||||
|
||||
writer := mockWriter{}
|
||||
logger := log.New(&writer)
|
||||
|
||||
for desc, tc := range cases {
|
||||
logger.Warn(tc.input)
|
||||
output, err := writer.Read()
|
||||
writer = mockWriter{}
|
||||
logger, err = log.New(&writer, tc.logLevel)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", desc, err))
|
||||
logger.Warn(tc.input)
|
||||
output, err = writer.Read()
|
||||
assert.Equal(t, tc.output, output, fmt.Sprintf("%s: expected %s got %s", desc, tc.output, output))
|
||||
}
|
||||
}
|
||||
@@ -89,7 +119,7 @@ func TestError(t *testing.T) {
|
||||
}
|
||||
|
||||
writer := mockWriter{}
|
||||
logger := log.New(&writer)
|
||||
logger, _ := log.New(&writer, log.Error.String())
|
||||
|
||||
for desc, tc := range cases {
|
||||
logger.Error(tc.input)
|
||||
|
||||
+13
-11
@@ -9,16 +9,17 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------------------|---------------------|-----------------------|
|
||||
| MF_MQTT_ADAPTER_PORT | Service MQTT port | 1883 |
|
||||
| MF_MQTT_WS_PORT | WebSocket port | 8880 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_MQTT_REDIS_PORT | Redis port | 6379 |
|
||||
| MF_MQTT_REDIS_HOST | Redis host | localhost |
|
||||
| MF_MQTT_REDIS_PASS | Redis pass | mqtt |
|
||||
| MF_MQTT_REDIS_DB | Redis db | 0 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
| Variable | Description | Default |
|
||||
|-----------------------------|------------------------|-----------------------|
|
||||
| MF_MQTT_ADAPTER_LOG_LEVEL | MQTT adapter log level | error |
|
||||
| MF_MQTT_ADAPTER_PORT | Service MQTT port | 1883 |
|
||||
| MF_MQTT_WS_PORT | WebSocket port | 8880 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_MQTT_REDIS_PORT | Redis port | 6379 |
|
||||
| MF_MQTT_REDIS_HOST | Redis host | localhost |
|
||||
| MF_MQTT_REDIS_PASS | Redis pass | mqtt |
|
||||
| MF_MQTT_REDIS_DB | Redis db | 0 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -36,6 +37,7 @@ services:
|
||||
environment:
|
||||
MF_THINGS_URL: [Things service URL]
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_MQTT_ADAPTER_LOG_LEVEL: [MQTT adapter log level]
|
||||
MF_MQTT_ADAPTER_PORT: [Service MQTT port]
|
||||
MF_MQTT_WS_PORT: [Service WS port]
|
||||
MF_MQTT_REDIS_PORT: [Redis port]
|
||||
@@ -56,7 +58,7 @@ cd $GOPATH/src/github.com/mainflux/mainflux/mqtt
|
||||
npm install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_MQTT_ADAPTER_PORT=[Service MQTT port] MF_MQTT_WS_PORT=[Service WS port] MF_MQTT_REDIS_PORT=[Redis port] MF_MQTT_REDIS_HOST=[Redis host] MF_MQTT_REDIS_PASS=[Redis pass] MF_MQTT_REDIS_DB=[Redis db] node mqtt.js ..
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_MQTT_ADAPTER_LOG_LEVEL=[MQTT adapter log level] MF_MQTT_ADAPTER_PORT=[Service MQTT port] MF_MQTT_WS_PORT=[Service WS port] MF_MQTT_REDIS_PORT=[Redis port] MF_MQTT_REDIS_HOST=[Redis host] MF_MQTT_REDIS_PASS=[Redis pass] MF_MQTT_REDIS_DB=[Redis db] node mqtt.js ..
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
+9
-4
@@ -12,6 +12,7 @@ var http = require('http'),
|
||||
// pass a proto file as a buffer/string or pass a parsed protobuf-schema object
|
||||
var logger = bunyan.createLogger({name: "mqtt"}),
|
||||
config = {
|
||||
log_level: process.env.MF_MQTT_ADAPTER_LOG_LEVEL || 'error',
|
||||
mqtt_port: Number(process.env.MF_MQTT_ADAPTER_PORT) || 1883,
|
||||
ws_port: Number(process.env.MF_MQTT_WS_PORT) || 8880,
|
||||
nats_url: process.env.MF_NATS_URL || 'nats://localhost:4222',
|
||||
@@ -42,9 +43,12 @@ var logger = bunyan.createLogger({name: "mqtt"}),
|
||||
|
||||
logging({
|
||||
instance: aedes,
|
||||
servers: servers
|
||||
servers: servers,
|
||||
pinoOptions: {level: config.log_level}
|
||||
});
|
||||
|
||||
logger.level(config.log_level);
|
||||
|
||||
// MQTT over WebSocket
|
||||
function startWs() {
|
||||
var server = http.createServer();
|
||||
@@ -59,6 +63,8 @@ function startMqtt() {
|
||||
|
||||
nats.subscribe('channel.*', function (msg) {
|
||||
var m = message.RawMessage.decode(Buffer.from(msg)),
|
||||
packet;
|
||||
if (m && m.Protocol !== 'mqtt') {
|
||||
packet = {
|
||||
cmd: 'publish',
|
||||
qos: 2,
|
||||
@@ -67,7 +73,8 @@ nats.subscribe('channel.*', function (msg) {
|
||||
retain: false
|
||||
};
|
||||
|
||||
aedes.publish(packet);
|
||||
aedes.publish(packet);
|
||||
}
|
||||
});
|
||||
|
||||
aedes.authorizePublish = function (client, packet, publish) {
|
||||
@@ -96,8 +103,6 @@ aedes.authorizePublish = function (client, packet, publish) {
|
||||
});
|
||||
nats.publish('channel.' + channelId, rawMsg);
|
||||
|
||||
// Set empty topic for packet so that it won't be published two times.
|
||||
packet.topic = '';
|
||||
publish(0);
|
||||
} else {
|
||||
logger.warn("unauthorized publish: %s", err.message);
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"dependencies": {
|
||||
"2": "^1.0.2",
|
||||
"aedes": "^0.35.2",
|
||||
"aedes-logging": "^1.0.1",
|
||||
"aedes-logging": "^2.0.1",
|
||||
"aedes-persistence-redis": "^5.1.0",
|
||||
"atob": "^2.0.3",
|
||||
"bunyan": "^1.5.1",
|
||||
|
||||
@@ -9,10 +9,11 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|--------------------|------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_NORMALIZER_PORT | Normalizer service HTTP port | 8180 |
|
||||
| Variable | Description | Default |
|
||||
|---------------------------|------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_NORMALIZER_LOG_LEVEL | Log level for the Normalizer | error |
|
||||
| MF_NORMALIZER_PORT | Normalizer service HTTP port | 8180 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -28,6 +29,7 @@ services:
|
||||
container_name: [instance name]
|
||||
environment:
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_NORMALIZER_LOG_LEVEL: [Normalizer log level]
|
||||
MF_NORMALIZER_PORT: [Service HTTP port]
|
||||
```
|
||||
|
||||
@@ -46,5 +48,5 @@ make normalizer
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_NATS_URL=[NATS instance URL] MF_NORMALIZER_PORT=[Service HTTP port] $GOBIN/mainflux-normalizer
|
||||
MF_NATS_URL=[NATS instance URL] MF_NORMALIZER_LOG_LEVEL=[Normalizer log level] MF_NORMALIZER_PORT=[Service HTTP port] $GOBIN/mainflux-normalizer
|
||||
```
|
||||
|
||||
@@ -12,19 +12,19 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/normalizer"
|
||||
)
|
||||
|
||||
var _ normalizer.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
logger logger.Logger
|
||||
svc normalizer.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the core service.
|
||||
func LoggingMiddleware(svc normalizer.Service, logger log.Logger) normalizer.Service {
|
||||
func LoggingMiddleware(svc normalizer.Service, logger logger.Logger) normalizer.Service {
|
||||
return &loggingMiddleware{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/normalizer"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
"github.com/nats-io/go-nats"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -14,19 +14,19 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
)
|
||||
|
||||
var _ readers.MessageRepository = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
logger logger.Logger
|
||||
svc readers.MessageRepository
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the core service.
|
||||
func LoggingMiddleware(svc readers.MessageRepository, logger log.Logger) readers.MessageRepository {
|
||||
func LoggingMiddleware(svc readers.MessageRepository, logger logger.Logger) readers.MessageRepository {
|
||||
return &loggingMiddleware{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
|
||||
@@ -63,11 +63,12 @@ execute following command:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/docker-compose.yml up -d
|
||||
./docker/addons/cassandra-reader/init.sh
|
||||
./docker/addons/cassandra-writer/init.sh
|
||||
docker-compose -f docker/addons/casandra-reader/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Service exposes HTTP API for fetching messages.
|
||||
Service exposes [HTTP API][doc] for fetching messages.
|
||||
|
||||
[doc]: ../swagger.yml
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
dockertest "gopkg.in/ory-am/dockertest.v3"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stdout)
|
||||
var logger, _ = log.New(os.Stdout, log.Info.String())
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
|
||||
@@ -70,6 +70,6 @@ docker-compose -f docker/addons/influxdb-reader/docker-compose.yml up -d
|
||||
|
||||
## Usage
|
||||
|
||||
Service exposes HTTP API for fetching messages.
|
||||
Service exposes [HTTP API][doc] for fetching messages.
|
||||
|
||||
[doc]: ../swagger.yml
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
@@ -33,14 +34,14 @@ var (
|
||||
Publisher: 1,
|
||||
Protocol: "mqtt",
|
||||
}
|
||||
testLog = log.New(os.Stdout)
|
||||
testLog, _ = log.New(os.Stdout, log.Info.String())
|
||||
)
|
||||
|
||||
func TestReadAll(t *testing.T) {
|
||||
client, err := influxdata.NewHTTPClient(clientCfg)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB client expected to succeed: %s.\n", err))
|
||||
|
||||
writer, err := writer.New(client, testDB)
|
||||
writer, err := writer.New(client, testDB, 1, time.Second)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB writer expected to succeed: %s.\n", err))
|
||||
|
||||
messages := []mainflux.Message{}
|
||||
|
||||
@@ -69,6 +69,6 @@ docker-compose -f docker/addons/mongodb-reader/docker-compose.yml up -d
|
||||
|
||||
## Usage
|
||||
|
||||
Service exposes HTTP API for fetching messages.
|
||||
Service exposes [HTTP API][doc] for fetching messages.
|
||||
|
||||
[doc]: ../swagger.yml
|
||||
|
||||
@@ -39,7 +39,7 @@ var (
|
||||
Publisher: 1,
|
||||
Protocol: "mqtt",
|
||||
}
|
||||
testLog = log.New(os.Stdout)
|
||||
testLog, _ = log.New(os.Stdout, log.Info.String())
|
||||
)
|
||||
|
||||
func TestReadAll(t *testing.T) {
|
||||
|
||||
+5
-5
@@ -30,29 +30,29 @@ gnatsd &
|
||||
###
|
||||
# Users
|
||||
###
|
||||
$BUILD_DIR/mainflux-users &
|
||||
MF_USERS_LOG_LEVEL=info $BUILD_DIR/mainflux-users &
|
||||
|
||||
###
|
||||
# Things
|
||||
###
|
||||
MF_THINGS_HTTP_PORT=8182 MF_THINGS_GRPC_PORT=8183 $BUILD_DIR/mainflux-things &
|
||||
MF_THINGS_LOG_LEVEL=info MF_THINGS_HTTP_PORT=8182 MF_THINGS_GRPC_PORT=8183 $BUILD_DIR/mainflux-things &
|
||||
|
||||
###
|
||||
# HTTP
|
||||
###
|
||||
MF_HTTP_ADAPTER_PORT=8185 MF_THINGS_URL=localhost:8183 $BUILD_DIR/mainflux-http &
|
||||
MF_HTTP_ADAPTER_LOG_LEVEL=info MF_HTTP_ADAPTER_PORT=8185 MF_THINGS_URL=localhost:8183 $BUILD_DIR/mainflux-http &
|
||||
|
||||
###
|
||||
# WS
|
||||
###
|
||||
MF_WS_ADAPTER_PORT=8186 MF_THINGS_URL=localhost:8183 $BUILD_DIR/mainflux-ws &
|
||||
MF_WS_ADAPTER_LOG_LEVEL=info MF_WS_ADAPTER_PORT=8186 MF_THINGS_URL=localhost:8183 $BUILD_DIR/mainflux-ws &
|
||||
|
||||
###
|
||||
# MQTT
|
||||
###
|
||||
# Switch to top dir to find *.proto stuff when running MQTT broker
|
||||
cd ..
|
||||
MF_THINGS_URL=localhost:8183 node mqtt/mqtt.js &
|
||||
MF_MQTT_ADAPTER_LOG_LEVEL=info MF_THINGS_URL=localhost:8183 node mqtt/mqtt.js &
|
||||
cd -
|
||||
|
||||
###
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Mainflux Go SDK
|
||||
|
||||
Go SDK, a Go driver for Mainflux HTTP API.
|
||||
|
||||
Does both system administration (provisioning) and messaging.
|
||||
|
||||
## Installation
|
||||
Import `"github.com/mainflux/mainflux/sdk/go"` in your Go package.
|
||||
|
||||
```
|
||||
import "github.com/mainflux/mainflux/sdk/go"
|
||||
```
|
||||
|
||||
Then call SDK Go functions to interact with the system.
|
||||
|
||||
## API Reference
|
||||
|
||||
```go
|
||||
FUNCTIONS
|
||||
|
||||
func NewMfxSDK(host, port string, tls bool) *MfxSDK
|
||||
|
||||
func (sdk *MfxSDK) Channel(id, token string) (things.Channel, error)
|
||||
Channel - gets channel by ID
|
||||
|
||||
func (sdk *MfxSDK) Channels(token string) ([]things.Channel, error)
|
||||
Channels - gets all channels
|
||||
|
||||
func (sdk *MfxSDK) ConnectThing(thingID, chanID, token string) error
|
||||
ConnectThing - connect thing to a channel
|
||||
|
||||
func (sdk *MfxSDK) CreateChannel(data, token string) (string, error)
|
||||
CreateChannel - creates new channel and generates UUID
|
||||
|
||||
func (sdk *MfxSDK) CreateThing(data, token string) (string, error)
|
||||
CreateThing - creates new thing and generates thing UUID
|
||||
|
||||
func (sdk *MfxSDK) CreateToken(user, pwd string) (string, error)
|
||||
CreateToken - create user token
|
||||
|
||||
func (sdk *MfxSDK) CreateUser(user, pwd string) error
|
||||
CreateUser - create user
|
||||
|
||||
func (sdk *MfxSDK) DeleteChannel(id, token string) error
|
||||
DeleteChannel - removes channel
|
||||
|
||||
func (sdk *MfxSDK) DeleteThing(id, token string) error
|
||||
DeleteThing - removes thing
|
||||
|
||||
func (sdk *MfxSDK) DisconnectThing(thingID, chanID, token string) error
|
||||
DisconnectThing - connect thing to a channel
|
||||
|
||||
func (sdk mfSDK) SendMessage(chanID, msg, token string) error
|
||||
SendMessage - send message on Mainflux channel
|
||||
|
||||
func (sdk mfSDK) SetContentType(ct ContentType) error
|
||||
SetContentType - set message content type. Available options are SenML
|
||||
JSON, custom JSON and custom binary (octet-stream).
|
||||
|
||||
func (sdk mfSDK) Thing(id, token string) (Thing, error)
|
||||
Thing - gets thing by ID
|
||||
|
||||
func (sdk mfSDK) Things(token string) ([]Thing, error)
|
||||
Things - gets all things
|
||||
|
||||
func (sdk mfSDK) UpdateChannel(channel Channel, token string) error
|
||||
UpdateChannel - update a channel
|
||||
|
||||
func (sdk mfSDK) UpdateThing(thing Thing, token string) error
|
||||
UpdateThing - updates thing by ID
|
||||
|
||||
func (sdk mfSDK) Version() (string, error)
|
||||
Version - server health check
|
||||
```
|
||||
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const channelsEndpoint = "channels"
|
||||
|
||||
func (sdk mfSDK) CreateChannel(channel Channel, token string) (string, error) {
|
||||
data, err := json.Marshal(channel)
|
||||
if err != nil {
|
||||
return "", ErrInvalidArgs
|
||||
}
|
||||
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, channelsEndpoint)
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return "", ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return "", ErrUnauthorized
|
||||
default:
|
||||
return "", ErrFailedCreation
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Header.Get("Location"), nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) Channels(token string, offset, limit uint64) ([]Channel, error) {
|
||||
endpoint := fmt.Sprintf("%s?offset=%d&limit=%d", channelsEndpoint, offset, limit)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return nil, ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return nil, ErrUnauthorized
|
||||
default:
|
||||
return nil, ErrFetchFailed
|
||||
}
|
||||
}
|
||||
|
||||
var l listChannelsRes
|
||||
if err := json.Unmarshal(body, &l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.Channels, nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) Channel(id, token string) (Channel, error) {
|
||||
endpoint := fmt.Sprintf("%s/%s", channelsEndpoint, id)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusForbidden:
|
||||
return Channel{}, ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return Channel{}, ErrNotFound
|
||||
default:
|
||||
return Channel{}, ErrFetchFailed
|
||||
}
|
||||
}
|
||||
|
||||
var c Channel
|
||||
if err := json.Unmarshal(body, &c); err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) UpdateChannel(channel Channel, token string) error {
|
||||
data, err := json.Marshal(channel)
|
||||
if err != nil {
|
||||
return ErrInvalidArgs
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s", channelsEndpoint, channel.ID)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedUpdate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) DeleteChannel(id, token string) error {
|
||||
endpoint := fmt.Sprintf("%s/%s", channelsEndpoint, id)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedUpdate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (sdk mfSDK) SendMessage(chanID, msg, token string) error {
|
||||
endpoint := fmt.Sprintf("channels/%s/messages", chanID)
|
||||
url := createURL(sdk.url, sdk.httpAdapterPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(sdk.msgContentType))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedUpdate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk *mfSDK) SetContentType(ct ContentType) error {
|
||||
if ct != CTJSON && ct != CTJSONSenML && ct != CTBinary {
|
||||
return ErrInvalidContentType
|
||||
}
|
||||
|
||||
sdk.msgContentType = ct
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
type tokenRes struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
type listThingsRes struct {
|
||||
Things []Thing `json:"things,omitempty"`
|
||||
}
|
||||
|
||||
type listChannelsRes struct {
|
||||
Channels []Channel `json:"channels,omitempty"`
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
// CTJSON represents JSON content type.
|
||||
CTJSON ContentType = "application/json"
|
||||
|
||||
// CTJSONSenML represents JSON SenML content type.
|
||||
CTJSONSenML ContentType = "application/senml+json"
|
||||
|
||||
// CTBinary represents binary content type.
|
||||
CTBinary ContentType = "application/octet-stream"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrConflict indicates that create or update of entity failed because
|
||||
// entity with same name already exists.
|
||||
ErrConflict = errors.New("entity already exists")
|
||||
|
||||
// ErrFailedCreation indicates that entity creation failed.
|
||||
ErrFailedCreation = errors.New("failed to create entity")
|
||||
|
||||
// ErrFailedUpdate indicates that entity update failed.
|
||||
ErrFailedUpdate = errors.New("failed to update entity")
|
||||
|
||||
// ErrFailedRemoval indicates that entity removal failed.
|
||||
ErrFailedRemoval = errors.New("failed to remove entity")
|
||||
|
||||
// ErrFailedConnection indicates that connecting thing to channel failed.
|
||||
ErrFailedConnection = errors.New("failed to connect thing to channel")
|
||||
|
||||
// ErrFailedDisconnect indicates that disconnecting thing from a channel failed.
|
||||
ErrFailedDisconnect = errors.New("failed to connect thing to channel")
|
||||
|
||||
// ErrInvalidArgs indicates that invalid argument was passed.
|
||||
ErrInvalidArgs = errors.New("invalid argument passed")
|
||||
|
||||
// ErrFetchFailed indicates that fetching of entity data failed.
|
||||
ErrFetchFailed = errors.New("failed to fetch entity")
|
||||
|
||||
// ErrUnauthorized indicates unauthorized access.
|
||||
ErrUnauthorized = errors.New("unauthorized access")
|
||||
|
||||
// ErrNotFound indicates that entity doesn't exist.
|
||||
ErrNotFound = errors.New("entity not found")
|
||||
|
||||
// ErrInvalidContentType indicates that nonexistent message content type
|
||||
// was passed.
|
||||
ErrInvalidContentType = errors.New("Unknown Content Type")
|
||||
)
|
||||
|
||||
// ContentType represents all possible content types.
|
||||
type ContentType string
|
||||
|
||||
var _ SDK = (*mfSDK)(nil)
|
||||
|
||||
// User represents mainflux user its credentials.
|
||||
type User struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// Thing represents mainflux thing.
|
||||
type Thing struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Channel represents mainflux channel.
|
||||
type Channel struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Things []Thing `json:"connected,omitempty"`
|
||||
}
|
||||
|
||||
// SDK contains Mainflux API.
|
||||
type SDK interface {
|
||||
// CreateUser registers mainflux user.
|
||||
CreateUser(user User) error
|
||||
|
||||
// CreateToken receives credentials and returns user token.
|
||||
CreateToken(user User) (string, error)
|
||||
|
||||
// CreateThing registers new thing and returns its id.
|
||||
CreateThing(thing Thing, token string) (string, error)
|
||||
|
||||
// Things returns page of things.
|
||||
Things(token string, offset, limit uint64) ([]Thing, error)
|
||||
|
||||
// Thing returns thing object by id.
|
||||
Thing(id, token string) (Thing, error)
|
||||
|
||||
// UpdateThing updates existing thing.
|
||||
UpdateThing(thing Thing, token string) error
|
||||
|
||||
// DeleteThing removes existing thing.
|
||||
DeleteThing(id, token string) error
|
||||
|
||||
// ConnectThing connects thing to specified channel by id.
|
||||
ConnectThing(thingID, chanID, token string) error
|
||||
|
||||
// DisconnectThing disconnect thing from specified channel by id.
|
||||
DisconnectThing(thingID, chanID, token string) error
|
||||
|
||||
// CreateChannel creates new channel and returns its id.
|
||||
CreateChannel(channel Channel, token string) (string, error)
|
||||
|
||||
// Channels returns page of channels.
|
||||
Channels(token string, offset, limit uint64) ([]Channel, error)
|
||||
|
||||
// Channel returns channel data by id.
|
||||
Channel(id, token string) (Channel, error)
|
||||
|
||||
// UpdateChannel updates existing channel.
|
||||
UpdateChannel(channel Channel, token string) error
|
||||
|
||||
// DeleteChannel removes existing channel.
|
||||
DeleteChannel(id, token string) error
|
||||
|
||||
// SendMessage send message to specified channel.
|
||||
SendMessage(chanID, msg, token string) error
|
||||
|
||||
// SetContentType sets message content type.
|
||||
SetContentType(ct ContentType) error
|
||||
|
||||
// Version returns used mainflux version.
|
||||
Version() (string, error)
|
||||
}
|
||||
|
||||
type mfSDK struct {
|
||||
url string
|
||||
usersPrefix string
|
||||
thingsPrefix string
|
||||
httpAdapterPrefix string
|
||||
msgContentType ContentType
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// Config contains sdk configuration parameters.
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
UsersPrefix string
|
||||
ThingsPrefix string
|
||||
HTTPAdapterPrefix string
|
||||
MsgContentType ContentType
|
||||
TLSVerification bool
|
||||
}
|
||||
|
||||
// NewSDK returns new mainflux SDK instance.
|
||||
func NewSDK(conf Config) SDK {
|
||||
return &mfSDK{
|
||||
url: conf.BaseURL,
|
||||
usersPrefix: conf.UsersPrefix,
|
||||
thingsPrefix: conf.ThingsPrefix,
|
||||
httpAdapterPrefix: conf.HTTPAdapterPrefix,
|
||||
msgContentType: conf.MsgContentType,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: !conf.TLSVerification,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (sdk mfSDK) sendRequest(req *http.Request, token, contentType string) (*http.Response, error) {
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", token)
|
||||
}
|
||||
|
||||
if contentType != "" {
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
}
|
||||
|
||||
return sdk.client.Do(req)
|
||||
}
|
||||
|
||||
func createURL(baseURL, prefix, endpoint string) string {
|
||||
if prefix == "" {
|
||||
return fmt.Sprintf("%s/%s", baseURL, endpoint)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", baseURL, prefix, endpoint)
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const thingsEndpoint = "things"
|
||||
|
||||
func (sdk mfSDK) CreateThing(thing Thing, token string) (string, error) {
|
||||
data, err := json.Marshal(thing)
|
||||
if err != nil {
|
||||
return "", ErrInvalidArgs
|
||||
}
|
||||
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, thingsEndpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return "", ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return "", ErrUnauthorized
|
||||
case http.StatusConflict:
|
||||
return "", ErrConflict
|
||||
default:
|
||||
return "", ErrFailedCreation
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Header.Get("Location"), nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) Things(token string, offset, limit uint64) ([]Thing, error) {
|
||||
endpoint := fmt.Sprintf("%s?offset=%d&limit=%d", thingsEndpoint, offset, limit)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return nil, ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return nil, ErrUnauthorized
|
||||
default:
|
||||
return nil, ErrFetchFailed
|
||||
}
|
||||
}
|
||||
|
||||
var l listThingsRes
|
||||
if err := json.Unmarshal(body, &l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.Things, nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) Thing(id, token string) (Thing, error) {
|
||||
endpoint := fmt.Sprintf("%s/%s", thingsEndpoint, id)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return Thing{}, err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return Thing{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Thing{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusForbidden:
|
||||
return Thing{}, ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return Thing{}, ErrNotFound
|
||||
default:
|
||||
return Thing{}, ErrFetchFailed
|
||||
}
|
||||
}
|
||||
|
||||
var t Thing
|
||||
if err := json.Unmarshal(body, &t); err != nil {
|
||||
return Thing{}, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) UpdateThing(thing Thing, token string) error {
|
||||
data, err := json.Marshal(thing)
|
||||
if err != nil {
|
||||
return ErrInvalidArgs
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s", thingsEndpoint, thing.ID)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedUpdate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) DeleteThing(id, token string) error {
|
||||
endpoint := fmt.Sprintf("%s/%s", thingsEndpoint, id)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
default:
|
||||
return ErrFailedRemoval
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) ConnectThing(thingID, chanID, token string) error {
|
||||
endpoint := fmt.Sprintf("%s/%s/%s/%s", channelsEndpoint, chanID, thingsEndpoint, thingID)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedConnection
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) DisconnectThing(thingID, chanID, token string) error {
|
||||
endpoint := fmt.Sprintf("%s/%s/%s/%s", channelsEndpoint, chanID, thingsEndpoint, thingID)
|
||||
url := createURL(sdk.url, sdk.thingsPrefix, endpoint)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := sdk.sendRequest(req, token, string(CTJSON))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusNotFound:
|
||||
return ErrNotFound
|
||||
default:
|
||||
return ErrFailedDisconnect
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (sdk mfSDK) CreateUser(user User) error {
|
||||
data, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return ErrInvalidArgs
|
||||
}
|
||||
|
||||
url := createURL(sdk.url, sdk.usersPrefix, "users")
|
||||
|
||||
resp, err := sdk.client.Post(url, string(CTJSON), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return ErrUnauthorized
|
||||
case http.StatusConflict:
|
||||
return ErrConflict
|
||||
default:
|
||||
return ErrFailedCreation
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk mfSDK) CreateToken(user User) (string, error) {
|
||||
data, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return "", ErrInvalidArgs
|
||||
}
|
||||
|
||||
url := createURL(sdk.url, sdk.usersPrefix, "tokens")
|
||||
|
||||
resp, err := sdk.client.Post(url, string(CTJSON), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
return "", ErrInvalidArgs
|
||||
case http.StatusForbidden:
|
||||
return "", ErrUnauthorized
|
||||
default:
|
||||
return "", ErrFailedCreation
|
||||
}
|
||||
}
|
||||
|
||||
var t tokenRes
|
||||
if err := json.Unmarshal(body, &t); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return t.Token, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type version struct {
|
||||
Value string `json:"version"`
|
||||
}
|
||||
|
||||
func (sdk mfSDK) Version() (string, error) {
|
||||
url := fmt.Sprintf("%s/version", sdk.url)
|
||||
|
||||
resp, err := sdk.client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var ver version
|
||||
if err := json.Unmarshal(body, &ver); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ver.Value, nil
|
||||
}
|
||||
+16
-14
@@ -17,19 +17,20 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|-----------------------|------------------------------------------|----------------|
|
||||
| MF_THINGS_DB_HOST | Database host address | localhost |
|
||||
| MF_THINGS_DB_PORT | Database host port | 5432 |
|
||||
| MF_THINGS_DB_USER | Database user | mainflux |
|
||||
| MF_THINGS_DB_PASS | Database password | mainflux |
|
||||
| MF_THINGS_DB | Name of the database used by the service | things |
|
||||
| MF_THINGS_CACHE_URL | Cache database URL | localhost:6379 |
|
||||
| MF_THINGS_CACHE_PASS | Cache database password | |
|
||||
| MF_THINGS_CACHE_DB | Cache instance that should be used | 0 |
|
||||
| MF_THINGS_HTTP_PORT | Things service HTTP port | 8180 |
|
||||
| MF_THINGS_GRPC_PORT | Things service gRPC port | 8181 |
|
||||
| MF_USERS_URL | Users service URL | localhost:8181 |
|
||||
| Variable | Description | Default |
|
||||
|-----------------------|-------------------------------------------------|----------------|
|
||||
| MF_THINGS_LOG_LEVEL | Log level for Things (debug, info, warn, error) | error |
|
||||
| MF_THINGS_DB_HOST | Database host address | localhost |
|
||||
| MF_THINGS_DB_PORT | Database host port | 5432 |
|
||||
| MF_THINGS_DB_USER | Database user | mainflux |
|
||||
| MF_THINGS_DB_PASS | Database password | mainflux |
|
||||
| MF_THINGS_DB | Name of the database used by the service | things |
|
||||
| MF_THINGS_CACHE_URL | Cache database URL | localhost:6379 |
|
||||
| MF_THINGS_CACHE_PASS | Cache database password | |
|
||||
| MF_THINGS_CACHE_DB | Cache instance that should be used | 0 |
|
||||
| MF_THINGS_HTTP_PORT | Things service HTTP port | 8180 |
|
||||
| MF_THINGS_GRPC_PORT | Things service gRPC port | 8181 |
|
||||
| MF_USERS_URL | Users service URL | localhost:8181 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -46,6 +47,7 @@ services:
|
||||
ports:
|
||||
- [host machine port]:[configured HTTP port]
|
||||
environment:
|
||||
MF_THINGS_LOG_LEVEL: [Things log level]
|
||||
MF_THINGS_DB_HOST: [Database host address]
|
||||
MF_THINGS_DB_PORT: [Database host port]
|
||||
MF_THINGS_DB_USER: [Database user]
|
||||
@@ -75,7 +77,7 @@ make things
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_CACHE_URL=[Cache database URL] MF_THINGS_CACHE_PASS=[Cache database password] MF_THINGS_CACHE_DB=[Cache instance that should be used] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-things
|
||||
MF_THINGS_LOG_LEVEL=[Things log level] MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_CACHE_URL=[Cache database URL] MF_THINGS_CACHE_PASS=[Cache database password] MF_THINGS_CACHE_DB=[Cache instance that should be used] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-things
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -47,10 +47,30 @@ func TestCanAccess(t *testing.T) {
|
||||
thingID uint64
|
||||
code codes.Code
|
||||
}{
|
||||
"check if connected thing can access existing channel": {key: cth.Key, chanID: sch.ID, thingID: cth.ID, code: codes.OK},
|
||||
"check if unconnected thing can access existing channel": {key: oth.Key, chanID: sch.ID, thingID: wrongID, code: codes.PermissionDenied},
|
||||
"check if thing with wrong access key can access existing channel": {key: wrong, chanID: sch.ID, thingID: wrongID, code: codes.PermissionDenied},
|
||||
"check if connected thing can access non-existent channel": {key: cth.Key, chanID: wrongID, thingID: wrongID, code: codes.InvalidArgument},
|
||||
"check if connected thing can access existing channel": {
|
||||
key: cth.Key,
|
||||
chanID: sch.ID,
|
||||
thingID: cth.ID,
|
||||
code: codes.OK,
|
||||
},
|
||||
"check if unconnected thing can access existing channel": {
|
||||
key: oth.Key,
|
||||
chanID: sch.ID,
|
||||
thingID: wrongID,
|
||||
code: codes.PermissionDenied,
|
||||
},
|
||||
"check if thing with wrong access key can access existing channel": {
|
||||
key: wrong,
|
||||
chanID: sch.ID,
|
||||
thingID: wrongID,
|
||||
code: codes.PermissionDenied,
|
||||
},
|
||||
"check if connected thing can access non-existent channel": {
|
||||
key: cth.Key,
|
||||
chanID: wrongID,
|
||||
thingID: wrongID,
|
||||
code: codes.InvalidArgument,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -76,8 +96,16 @@ func TestIdentify(t *testing.T) {
|
||||
id uint64
|
||||
code codes.Code
|
||||
}{
|
||||
"identify existing thing": {key: sth.Key, id: sth.ID, code: codes.OK},
|
||||
"identify non-existent thing": {key: wrong, id: wrongID, code: codes.PermissionDenied},
|
||||
"identify existing thing": {
|
||||
key: sth.Key,
|
||||
id: sth.ID,
|
||||
code: codes.OK,
|
||||
},
|
||||
"identify non-existent thing": {
|
||||
key: wrong,
|
||||
id: wrongID,
|
||||
code: codes.PermissionDenied,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/mainflux/mainflux/things"
|
||||
)
|
||||
import "github.com/mainflux/mainflux/things"
|
||||
|
||||
type accessReq struct {
|
||||
thingKey string
|
||||
|
||||
+160
-20
@@ -9,6 +9,7 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/mainflux/mainflux/things"
|
||||
@@ -22,12 +23,21 @@ func addThingEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saved, err := svc.AddThing(req.key, req.thing)
|
||||
thing := things.Thing{
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
saved, err := svc.AddThing(req.key, thing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return thingRes{id: saved.ID, created: true}, nil
|
||||
res := thingRes{
|
||||
id: strconv.FormatUint(saved.ID, 10),
|
||||
created: true,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +49,24 @@ func updateThingEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.thing.ID = req.id
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.UpdateThing(req.key, req.thing); err != nil {
|
||||
thing := things.Thing{
|
||||
ID: id,
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
if err := svc.UpdateThing(req.key, thing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return thingRes{id: req.id, created: false}, nil
|
||||
res := thingRes{id: req.id, created: false}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +78,25 @@ func viewThingEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
thing, err := svc.ViewThing(req.key, req.id)
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
thing, err := svc.ViewThing(req.key, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return viewThingRes{thing}, nil
|
||||
res := viewThingRes{
|
||||
ID: strconv.FormatUint(thing.ID, 10),
|
||||
Owner: thing.Owner,
|
||||
Type: thing.Type,
|
||||
Name: thing.Name,
|
||||
Key: thing.Key,
|
||||
Metadata: thing.Metadata,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +113,20 @@ func listThingsEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return listThingsRes{Things: things}, nil
|
||||
res := listThingsRes{}
|
||||
for _, thing := range things {
|
||||
view := viewThingRes{
|
||||
ID: strconv.FormatUint(thing.ID, 10),
|
||||
Owner: thing.Owner,
|
||||
Type: thing.Type,
|
||||
Name: thing.Name,
|
||||
Key: thing.Key,
|
||||
Metadata: thing.Metadata,
|
||||
}
|
||||
res.Things = append(res.Things, view)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +143,12 @@ func removeThingEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = svc.RemoveThing(req.key, req.id); err != nil {
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.RemoveThing(req.key, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -112,12 +164,17 @@ func createChannelEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saved, err := svc.CreateChannel(req.key, req.channel)
|
||||
channel := things.Channel{Name: req.Name}
|
||||
saved, err := svc.CreateChannel(req.key, channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channelRes{id: saved.ID, created: true}, nil
|
||||
res := channelRes{
|
||||
id: strconv.FormatUint(saved.ID, 10),
|
||||
created: true,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +186,24 @@ func updateChannelEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.channel.ID = req.id
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.UpdateChannel(req.key, req.channel); err != nil {
|
||||
channel := things.Channel{
|
||||
ID: id,
|
||||
Name: req.Name,
|
||||
}
|
||||
if err := svc.UpdateChannel(req.key, channel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channelRes{id: req.id, created: false}, nil
|
||||
res := channelRes{
|
||||
id: strconv.FormatUint(id, 10),
|
||||
created: false,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +215,34 @@ func viewChannelEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := svc.ViewChannel(req.key, req.id)
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
channel, err := svc.ViewChannel(req.key, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return viewChannelRes{channel}, nil
|
||||
res := viewChannelRes{
|
||||
ID: strconv.FormatUint(channel.ID, 10),
|
||||
Owner: channel.Owner,
|
||||
Name: channel.Name,
|
||||
}
|
||||
for _, thing := range channel.Things {
|
||||
view := viewThingRes{
|
||||
ID: strconv.FormatUint(thing.ID, 10),
|
||||
Owner: thing.Owner,
|
||||
Type: thing.Type,
|
||||
Name: thing.Name,
|
||||
Key: thing.Key,
|
||||
Metadata: thing.Metadata,
|
||||
}
|
||||
res.Things = append(res.Things, view)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +259,32 @@ func listChannelsEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return listChannelsRes{Channels: channels}, nil
|
||||
res := listChannelsRes{}
|
||||
// Cast channels
|
||||
for _, channel := range channels {
|
||||
cView := viewChannelRes{
|
||||
ID: strconv.FormatUint(channel.ID, 10),
|
||||
Owner: channel.Owner,
|
||||
Name: channel.Name,
|
||||
}
|
||||
|
||||
// Cast things
|
||||
for _, thing := range channel.Things {
|
||||
tView := viewThingRes{
|
||||
ID: strconv.FormatUint(thing.ID, 10),
|
||||
Owner: thing.Owner,
|
||||
Type: thing.Type,
|
||||
Name: thing.Name,
|
||||
Key: thing.Key,
|
||||
Metadata: thing.Metadata,
|
||||
}
|
||||
cView.Things = append(cView.Things, tView)
|
||||
}
|
||||
|
||||
res.Channels = append(res.Channels, cView)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +299,12 @@ func removeChannelEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.RemoveChannel(req.key, req.id); err != nil {
|
||||
id, err := strconv.ParseUint(req.id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.RemoveChannel(req.key, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -200,7 +320,17 @@ func connectEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.Connect(cr.key, cr.chanID, cr.thingID); err != nil {
|
||||
chanID, err := strconv.ParseUint(cr.chanID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
thingID, err := strconv.ParseUint(cr.thingID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.Connect(cr.key, chanID, thingID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -216,7 +346,17 @@ func disconnectEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.Disconnect(cr.key, cr.chanID, cr.thingID); err != nil {
|
||||
chanID, err := strconv.ParseUint(cr.chanID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
thingID, err := strconv.ParseUint(cr.thingID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
if err := svc.Disconnect(cr.key, chanID, thingID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -120,6 +121,14 @@ func TestAddThing(t *testing.T) {
|
||||
status: http.StatusForbidden,
|
||||
location: "",
|
||||
},
|
||||
{
|
||||
desc: "add thing with empty auth token",
|
||||
req: data,
|
||||
contentType: contentType,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
location: "",
|
||||
},
|
||||
{
|
||||
desc: "add thing with invalid request format",
|
||||
req: "}",
|
||||
@@ -180,11 +189,12 @@ func TestUpdateThing(t *testing.T) {
|
||||
data := toJSON(thing)
|
||||
invalidData := toJSON(things.Thing{Type: "foo"})
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
sthID := strconv.FormatUint(sth.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
req string
|
||||
id uint64
|
||||
id string
|
||||
contentType string
|
||||
auth string
|
||||
status int
|
||||
@@ -192,7 +202,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update existing thing",
|
||||
req: data,
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
@@ -200,7 +210,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update non-existent thing",
|
||||
req: data,
|
||||
id: wrongID,
|
||||
id: strconv.FormatUint(wrongID, 10),
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
@@ -208,7 +218,15 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update thing with invalid data",
|
||||
req: invalidData,
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "update thing with invalid id",
|
||||
req: data,
|
||||
id: "invalid",
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -216,15 +234,23 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update thing with invalid user token",
|
||||
req: data,
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "update thing with empty user token",
|
||||
req: data,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "update thing with invalid data format",
|
||||
req: "{",
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -232,7 +258,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update thing with empty JSON request",
|
||||
req: "{}",
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -240,7 +266,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update thing with empty request",
|
||||
req: "",
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -248,7 +274,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
{
|
||||
desc: "update thing without content type",
|
||||
req: data,
|
||||
id: sth.ID,
|
||||
id: sthID,
|
||||
contentType: "",
|
||||
auth: token,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
@@ -259,7 +285,7 @@ func TestUpdateThing(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/things/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.auth,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -276,25 +302,66 @@ func TestViewThing(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
data := toJSON(sth)
|
||||
sthID := strconv.FormatUint(sth.ID, 10)
|
||||
|
||||
thres := thingRes{
|
||||
ID: sthID,
|
||||
Type: sth.Type,
|
||||
Name: sth.Name,
|
||||
Key: sth.Key,
|
||||
Metadata: sth.Metadata,
|
||||
}
|
||||
data := toJSON(thres)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
id string
|
||||
auth string
|
||||
status int
|
||||
res string
|
||||
}{
|
||||
{desc: "view existing thing", id: sth.ID, auth: token, status: http.StatusOK, res: data},
|
||||
{desc: "view non-existent thing", id: wrongID, auth: token, status: http.StatusNotFound, res: ""},
|
||||
{desc: "view thing by passing invalid token", id: sth.ID, auth: wrongValue, status: http.StatusForbidden, res: ""},
|
||||
{
|
||||
desc: "view existing thing",
|
||||
id: sthID,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
res: data,
|
||||
},
|
||||
{
|
||||
desc: "view non-existent thing",
|
||||
id: strconv.FormatUint(wrongID, 10),
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view thing by passing invalid token",
|
||||
id: sthID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view thing by passing empty token",
|
||||
id: sthID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view thing by passing invalid id",
|
||||
id: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
res: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/things/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -312,12 +379,17 @@ func TestListThings(t *testing.T) {
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
|
||||
data := []things.Thing{}
|
||||
data := []thingRes{}
|
||||
for i := 0; i < 101; i++ {
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
// must be "nulled" due to the JSON serialization that ignores owner
|
||||
sth.Owner = ""
|
||||
data = append(data, sth)
|
||||
thres := thingRes{
|
||||
ID: strconv.FormatUint(sth.ID, 10),
|
||||
Type: sth.Type,
|
||||
Name: sth.Name,
|
||||
Key: sth.Key,
|
||||
Metadata: sth.Metadata,
|
||||
}
|
||||
data = append(data, thres)
|
||||
}
|
||||
thingURL := fmt.Sprintf("%s/things", ts.URL)
|
||||
cases := []struct {
|
||||
@@ -325,7 +397,7 @@ func TestListThings(t *testing.T) {
|
||||
auth string
|
||||
status int
|
||||
url string
|
||||
res []things.Thing
|
||||
res []thingRes
|
||||
}{
|
||||
{
|
||||
desc: "get a list of things",
|
||||
@@ -341,6 +413,13 @@ func TestListThings(t *testing.T) {
|
||||
url: fmt.Sprintf("%s?offset=%d&limit=%d", thingURL, 0, 1),
|
||||
res: nil,
|
||||
},
|
||||
{
|
||||
desc: "get a list of things with empty token",
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
url: fmt.Sprintf("%s?offset=%d&limit=%d", thingURL, 0, 1),
|
||||
res: nil,
|
||||
},
|
||||
{
|
||||
desc: "get a list of things with negative offset",
|
||||
auth: token,
|
||||
@@ -436,7 +515,7 @@ func TestListThings(t *testing.T) {
|
||||
}
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var data map[string][]things.Thing
|
||||
var data map[string][]thingRes
|
||||
json.NewDecoder(res.Body).Decode(&data)
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
assert.ElementsMatch(t, tc.res, data["things"], fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, data["things"]))
|
||||
@@ -449,23 +528,51 @@ func TestRemoveThing(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
sthID := strconv.FormatUint(sth.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
id string
|
||||
auth string
|
||||
status int
|
||||
}{
|
||||
{desc: "delete existing thing", id: sth.ID, auth: token, status: http.StatusNoContent},
|
||||
{desc: "delete non-existent thing", id: wrongID, auth: token, status: http.StatusNoContent},
|
||||
{desc: "delete thing with invalid token", id: sth.ID, auth: wrongValue, status: http.StatusForbidden},
|
||||
{
|
||||
desc: "delete existing thing",
|
||||
id: sthID,
|
||||
auth: token,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "delete non-existent thing",
|
||||
id: strconv.FormatUint(wrongID, 10),
|
||||
auth: token,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "remove thing with invalid id",
|
||||
id: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "delete thing with invalid token",
|
||||
id: sthID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "delete thing with empty token",
|
||||
id: sthID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/things/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/things/%s", ts.URL, tc.id),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -505,6 +612,14 @@ func TestCreateChannel(t *testing.T) {
|
||||
status: http.StatusForbidden,
|
||||
location: "",
|
||||
},
|
||||
{
|
||||
desc: "create new channel with empty token",
|
||||
req: data,
|
||||
contentType: contentType,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
location: "",
|
||||
},
|
||||
{
|
||||
desc: "create new channel with invalid data format",
|
||||
req: "{",
|
||||
@@ -564,11 +679,12 @@ func TestUpdateChannel(t *testing.T) {
|
||||
|
||||
updateData := toJSON(map[string]string{"name": "updated_channel"})
|
||||
sch, _ := svc.CreateChannel(token, channel)
|
||||
schID := strconv.FormatUint(sch.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
req string
|
||||
id uint64
|
||||
id string
|
||||
contentType string
|
||||
auth string
|
||||
status int
|
||||
@@ -576,7 +692,7 @@ func TestUpdateChannel(t *testing.T) {
|
||||
{
|
||||
desc: "update existing channel",
|
||||
req: updateData,
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
@@ -584,23 +700,39 @@ func TestUpdateChannel(t *testing.T) {
|
||||
{
|
||||
desc: "update non-existing channel",
|
||||
req: updateData,
|
||||
id: wrongID,
|
||||
id: strconv.FormatUint(wrongID, 10),
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update channel with invalid id",
|
||||
req: updateData,
|
||||
id: "invalid",
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "update channel with invalid token",
|
||||
req: updateData,
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "update channel with empty token",
|
||||
req: updateData,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "update channel with invalid data format",
|
||||
req: "}",
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -608,7 +740,7 @@ func TestUpdateChannel(t *testing.T) {
|
||||
{
|
||||
desc: "update channel with empty JSON object",
|
||||
req: "{}",
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
@@ -616,7 +748,7 @@ func TestUpdateChannel(t *testing.T) {
|
||||
{
|
||||
desc: "update channel with empty request",
|
||||
req: "",
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: contentType,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -624,7 +756,7 @@ func TestUpdateChannel(t *testing.T) {
|
||||
{
|
||||
desc: "update channel with missing content type",
|
||||
req: updateData,
|
||||
id: sch.ID,
|
||||
id: schID,
|
||||
contentType: "",
|
||||
auth: token,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
@@ -635,7 +767,7 @@ func TestUpdateChannel(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/channels/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/channels/%s", ts.URL, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.auth,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -652,25 +784,75 @@ func TestViewChannel(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
sch, _ := svc.CreateChannel(token, channel)
|
||||
data := toJSON(sch)
|
||||
schID := strconv.FormatUint(sch.ID, 10)
|
||||
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
svc.Connect(token, sch.ID, sth.ID)
|
||||
|
||||
chres := channelRes{
|
||||
ID: strconv.FormatUint(sch.ID, 10),
|
||||
Name: sch.Name,
|
||||
Things: []thingRes{
|
||||
{
|
||||
ID: strconv.FormatUint(sth.ID, 10),
|
||||
Type: sth.Type,
|
||||
Name: sth.Name,
|
||||
Key: sth.Key,
|
||||
Metadata: sth.Metadata,
|
||||
},
|
||||
},
|
||||
}
|
||||
data := toJSON(chres)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
id string
|
||||
auth string
|
||||
status int
|
||||
res string
|
||||
}{
|
||||
{desc: "view existing channel", id: sch.ID, auth: token, status: http.StatusOK, res: data},
|
||||
{desc: "view non-existent channel", id: wrongID, auth: token, status: http.StatusNotFound, res: ""},
|
||||
{desc: "view channel with invalid token", id: sch.ID, auth: wrongValue, status: http.StatusForbidden, res: ""},
|
||||
{
|
||||
desc: "view existing channel",
|
||||
id: schID,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
res: data,
|
||||
},
|
||||
{
|
||||
desc: "view non-existent channel",
|
||||
id: strconv.FormatUint(wrongID, 10),
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view channel with invalid token",
|
||||
id: schID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view channel with empty token",
|
||||
id: schID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
res: "",
|
||||
},
|
||||
{
|
||||
desc: "view channel with invalid id",
|
||||
id: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
res: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/channels/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/channels/%s", ts.URL, tc.id),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -688,12 +870,26 @@ func TestListChannels(t *testing.T) {
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
|
||||
channels := []things.Channel{}
|
||||
channels := []channelRes{}
|
||||
for i := 0; i < 101; i++ {
|
||||
sch, _ := svc.CreateChannel(token, channel)
|
||||
// must be "nulled" due to the JSON serialization that ignores owner
|
||||
sch.Owner = ""
|
||||
channels = append(channels, sch)
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
svc.Connect(token, sch.ID, sth.ID)
|
||||
|
||||
chres := channelRes{
|
||||
ID: strconv.FormatUint(sch.ID, 10),
|
||||
Name: sch.Name,
|
||||
Things: []thingRes{
|
||||
{
|
||||
ID: strconv.FormatUint(sth.ID, 10),
|
||||
Type: sth.Type,
|
||||
Name: sth.Name,
|
||||
Key: sth.Key,
|
||||
Metadata: sth.Metadata,
|
||||
},
|
||||
},
|
||||
}
|
||||
channels = append(channels, chres)
|
||||
}
|
||||
channelURL := fmt.Sprintf("%s/channels", ts.URL)
|
||||
|
||||
@@ -702,7 +898,7 @@ func TestListChannels(t *testing.T) {
|
||||
auth string
|
||||
status int
|
||||
url string
|
||||
res []things.Channel
|
||||
res []channelRes
|
||||
}{
|
||||
{
|
||||
desc: "get a list of channels",
|
||||
@@ -718,6 +914,13 @@ func TestListChannels(t *testing.T) {
|
||||
url: fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 0, 1),
|
||||
res: nil,
|
||||
},
|
||||
{
|
||||
desc: "get a list of channels with empty token",
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
url: fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 0, 1),
|
||||
res: nil,
|
||||
},
|
||||
{
|
||||
desc: "get a list of channels with negative offset",
|
||||
auth: token,
|
||||
@@ -813,7 +1016,7 @@ func TestListChannels(t *testing.T) {
|
||||
}
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var body map[string][]things.Channel
|
||||
var body map[string][]channelRes
|
||||
json.NewDecoder(res.Body).Decode(&body)
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
assert.ElementsMatch(t, tc.res, body["channels"], fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, body["channels"]))
|
||||
@@ -826,23 +1029,57 @@ func TestRemoveChannel(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
sch, _ := svc.CreateChannel(token, channel)
|
||||
schID := strconv.FormatUint(sch.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
id string
|
||||
auth string
|
||||
status int
|
||||
}{
|
||||
{desc: "remove channel with invalid token", id: sch.ID, auth: wrongValue, status: http.StatusForbidden},
|
||||
{desc: "remove existing channel", id: sch.ID, auth: token, status: http.StatusNoContent},
|
||||
{desc: "remove removed channel", id: sch.ID, auth: token, status: http.StatusNoContent},
|
||||
{
|
||||
desc: "remove channel with invalid token",
|
||||
id: schID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "remove existing channel",
|
||||
id: schID,
|
||||
auth: token,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "remove removed channel",
|
||||
id: schID,
|
||||
auth: token,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "remove channel with invalid id",
|
||||
id: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "remove channel with invalid token",
|
||||
id: schID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "remove channel with empty token",
|
||||
id: schID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/channels/%d", ts.URL, tc.id),
|
||||
url: fmt.Sprintf("%s/channels/%s", ts.URL, tc.id),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -862,48 +1099,72 @@ func TestConnect(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
ath, _ := svc.AddThing(token, thing)
|
||||
athID := strconv.FormatUint(ath.ID, 10)
|
||||
ach, _ := svc.CreateChannel(token, channel)
|
||||
achID := strconv.FormatUint(ach.ID, 10)
|
||||
bch, _ := svc.CreateChannel(otherToken, channel)
|
||||
bchID := strconv.FormatUint(bch.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
chanID uint64
|
||||
thingID uint64
|
||||
chanID string
|
||||
thingID string
|
||||
auth string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "connect existing thing to existing channel",
|
||||
chanID: ach.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "connect existing thing to non-existent channel",
|
||||
chanID: wrongID,
|
||||
thingID: ath.ID,
|
||||
chanID: strconv.FormatUint(wrongID, 10),
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "connect non-existing thing to existing channel",
|
||||
chanID: ach.ID,
|
||||
thingID: wrongID,
|
||||
chanID: achID,
|
||||
thingID: strconv.FormatUint(wrongID, 10),
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "connect existing thing to channel with invalid id",
|
||||
chanID: "invalid",
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "connect thing with invalid id to existing channel",
|
||||
chanID: achID,
|
||||
thingID: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "connect existing thing to existing channel with invalid token",
|
||||
chanID: ach.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "connect existing thing to existing channel with empty token",
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "connect thing from owner to channel of other user",
|
||||
chanID: bch.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: bchID,
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
@@ -913,7 +1174,7 @@ func TestConnect(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/channels/%d/things/%d", ts.URL, tc.chanID, tc.thingID),
|
||||
url: fmt.Sprintf("%s/channels/%s/things/%s", ts.URL, tc.chanID, tc.thingID),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -933,66 +1194,92 @@ func TestDisconnnect(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
ath, _ := svc.AddThing(token, thing)
|
||||
athID := strconv.FormatUint(ath.ID, 10)
|
||||
|
||||
ach, _ := svc.CreateChannel(token, channel)
|
||||
achID := strconv.FormatUint(ach.ID, 10)
|
||||
|
||||
svc.Connect(token, ach.ID, ath.ID)
|
||||
bch, _ := svc.CreateChannel(otherToken, channel)
|
||||
bchID := strconv.FormatUint(bch.ID, 10)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
chanID uint64
|
||||
thingID uint64
|
||||
chanID string
|
||||
thingID string
|
||||
auth string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "disconnect connected thing from channel",
|
||||
chanID: ach.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-connected thing from channel",
|
||||
chanID: ach.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-existent thing from channel",
|
||||
chanID: ach.ID,
|
||||
thingID: wrongID,
|
||||
chanID: achID,
|
||||
thingID: strconv.FormatUint(wrongID, 10),
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect thing from non-existent channel",
|
||||
chanID: wrongID,
|
||||
thingID: ath.ID,
|
||||
chanID: strconv.FormatUint(wrongID, 10),
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect thing from channel with invalid token",
|
||||
chanID: ach.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: wrongValue,
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "disconnect thing from channel with empty token",
|
||||
chanID: achID,
|
||||
thingID: athID,
|
||||
auth: "",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "disconnect owner's thing from someone elses channel",
|
||||
chanID: bch.ID,
|
||||
thingID: ath.ID,
|
||||
chanID: bchID,
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect thing with invalid id from channel",
|
||||
chanID: achID,
|
||||
thingID: "invalid",
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "disconnect thing from channel with invalid id",
|
||||
chanID: "invalid",
|
||||
thingID: athID,
|
||||
auth: token,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/channels/%d/things/%d", ts.URL, tc.chanID, tc.thingID),
|
||||
url: fmt.Sprintf("%s/channels/%s/things/%s", ts.URL, tc.chanID, tc.thingID),
|
||||
token: tc.auth,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -1000,3 +1287,17 @@ func TestDisconnnect(t *testing.T) {
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
type thingRes struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type channelRes struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Things []thingRes `json:"connected,omitempty"`
|
||||
}
|
||||
|
||||
+31
-37
@@ -7,9 +7,7 @@
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/mainflux/mainflux/things"
|
||||
)
|
||||
import "github.com/mainflux/mainflux/things"
|
||||
|
||||
const maxLimitSize = 100
|
||||
|
||||
@@ -30,8 +28,10 @@ func (req identityReq) validate() error {
|
||||
}
|
||||
|
||||
type addThingReq struct {
|
||||
key string
|
||||
thing things.Thing
|
||||
key string
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (req addThingReq) validate() error {
|
||||
@@ -39,13 +39,19 @@ func (req addThingReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
return req.thing.Validate()
|
||||
if req.Type == "" {
|
||||
return things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type updateThingReq struct {
|
||||
key string
|
||||
id uint64
|
||||
thing things.Thing
|
||||
key string
|
||||
id string
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (req updateThingReq) validate() error {
|
||||
@@ -53,16 +59,16 @@ func (req updateThingReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if req.id < 1 {
|
||||
return things.ErrNotFound
|
||||
if req.Type == "" {
|
||||
return things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return req.thing.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
type createChannelReq struct {
|
||||
key string
|
||||
channel things.Channel
|
||||
key string
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (req createChannelReq) validate() error {
|
||||
@@ -74,9 +80,9 @@ func (req createChannelReq) validate() error {
|
||||
}
|
||||
|
||||
type updateChannelReq struct {
|
||||
key string
|
||||
id uint64
|
||||
channel things.Channel
|
||||
key string
|
||||
id string
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (req updateChannelReq) validate() error {
|
||||
@@ -84,16 +90,12 @@ func (req updateChannelReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if req.id < 1 {
|
||||
return things.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type viewResourceReq struct {
|
||||
key string
|
||||
id uint64
|
||||
id string
|
||||
}
|
||||
|
||||
func (req viewResourceReq) validate() error {
|
||||
@@ -101,17 +103,13 @@ func (req viewResourceReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if req.id < 1 {
|
||||
return things.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listResourcesReq struct {
|
||||
key string
|
||||
offset int
|
||||
limit int
|
||||
offset uint64
|
||||
limit uint64
|
||||
}
|
||||
|
||||
func (req *listResourcesReq) validate() error {
|
||||
@@ -119,17 +117,17 @@ func (req *listResourcesReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if req.offset >= 0 && req.limit > 0 && req.limit <= maxLimitSize {
|
||||
return nil
|
||||
if req.limit == 0 || req.limit > maxLimitSize {
|
||||
return things.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return things.ErrMalformedEntity
|
||||
return nil
|
||||
}
|
||||
|
||||
type connectionReq struct {
|
||||
key string
|
||||
chanID uint64
|
||||
thingID uint64
|
||||
chanID string
|
||||
thingID string
|
||||
}
|
||||
|
||||
func (req connectionReq) validate() error {
|
||||
@@ -137,9 +135,5 @@ func (req connectionReq) validate() error {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if req.chanID == 0 || req.thingID == 0 {
|
||||
return things.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux/things"
|
||||
@@ -21,8 +22,14 @@ func TestIdentityReqValidation(t *testing.T) {
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"non-empty token": {key: uuid.NewV4().String(), err: nil},
|
||||
"empty token": {key: "", err: things.ErrUnauthorizedAccess},
|
||||
"non-empty token": {
|
||||
key: uuid.NewV4().String(),
|
||||
err: nil,
|
||||
},
|
||||
"empty token": {
|
||||
key: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -35,22 +42,35 @@ func TestIdentityReqValidation(t *testing.T) {
|
||||
func TestAddThingReqValidation(t *testing.T) {
|
||||
key := uuid.NewV4().String()
|
||||
valid := things.Thing{Type: "app"}
|
||||
invalid := things.Thing{Type: "?"}
|
||||
invalid := things.Thing{ID: 0, Type: ""}
|
||||
|
||||
cases := map[string]struct {
|
||||
thing things.Thing
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid thing addition request": {thing: valid, key: key, err: nil},
|
||||
"missing token": {thing: valid, key: "", err: things.ErrUnauthorizedAccess},
|
||||
"wrong thing type": {thing: invalid, key: key, err: things.ErrMalformedEntity},
|
||||
"valid thing addition request": {
|
||||
thing: valid,
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
thing: valid,
|
||||
key: "", err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"empty thing type": {
|
||||
thing: invalid,
|
||||
key: key,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
req := addThingReq{
|
||||
key: tc.key,
|
||||
thing: tc.thing,
|
||||
key: tc.key,
|
||||
Name: tc.thing.Name,
|
||||
Type: tc.thing.Type,
|
||||
Metadata: tc.thing.Metadata,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
@@ -61,25 +81,41 @@ func TestAddThingReqValidation(t *testing.T) {
|
||||
func TestUpdateThingReqValidation(t *testing.T) {
|
||||
key := uuid.NewV4().String()
|
||||
valid := things.Thing{ID: 1, Type: "app"}
|
||||
invalid := things.Thing{ID: 0, Type: "?"}
|
||||
invalid := things.Thing{ID: 0, Type: ""}
|
||||
|
||||
cases := map[string]struct {
|
||||
thing things.Thing
|
||||
id uint64
|
||||
id string
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid thing update request": {thing: valid, id: valid.ID, key: key, err: nil},
|
||||
"invalid thing ID": {thing: valid, id: invalid.ID, key: key, err: things.ErrNotFound},
|
||||
"missing token": {thing: valid, id: valid.ID, key: "", err: things.ErrUnauthorizedAccess},
|
||||
"wrong thing type": {thing: invalid, id: valid.ID, key: key, err: things.ErrMalformedEntity},
|
||||
"valid thing update request": {
|
||||
thing: valid,
|
||||
id: strconv.FormatUint(valid.ID, 10),
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
thing: valid,
|
||||
id: strconv.FormatUint(valid.ID, 10),
|
||||
key: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"empty thing type": {
|
||||
thing: invalid,
|
||||
id: strconv.FormatUint(valid.ID, 10),
|
||||
key: key,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
req := updateThingReq{
|
||||
key: tc.key,
|
||||
id: tc.id,
|
||||
thing: tc.thing,
|
||||
key: tc.key,
|
||||
id: tc.id,
|
||||
Name: tc.thing.Name,
|
||||
Type: tc.thing.Type,
|
||||
Metadata: tc.thing.Metadata,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
@@ -96,14 +132,22 @@ func TestCreateChannelReqValidation(t *testing.T) {
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid channel creation request": {channel: channel, key: key, err: nil},
|
||||
"missing token": {channel: channel, key: "", err: things.ErrUnauthorizedAccess},
|
||||
"valid channel creation request": {
|
||||
channel: channel,
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
channel: channel,
|
||||
key: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
req := createChannelReq{
|
||||
key: tc.key,
|
||||
channel: tc.channel,
|
||||
key: tc.key,
|
||||
Name: tc.channel.Name,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
@@ -114,24 +158,32 @@ func TestCreateChannelReqValidation(t *testing.T) {
|
||||
func TestUpdateChannelReqValidation(t *testing.T) {
|
||||
key := uuid.NewV4().String()
|
||||
channel := things.Channel{ID: 1}
|
||||
wrongID := uint64(0)
|
||||
|
||||
cases := map[string]struct {
|
||||
channel things.Channel
|
||||
id uint64
|
||||
id string
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid channel update request": {channel: channel, id: channel.ID, key: key, err: nil},
|
||||
"invalid channel ID": {channel: channel, id: wrongID, key: key, err: things.ErrNotFound},
|
||||
"missing token": {channel: channel, id: channel.ID, key: "", err: things.ErrUnauthorizedAccess},
|
||||
"valid channel update request": {
|
||||
channel: channel,
|
||||
id: strconv.FormatUint(channel.ID, 10),
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
channel: channel,
|
||||
id: strconv.FormatUint(channel.ID, 10),
|
||||
key: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
req := updateChannelReq{
|
||||
key: tc.key,
|
||||
id: tc.id,
|
||||
channel: tc.channel,
|
||||
key: tc.key,
|
||||
id: tc.id,
|
||||
Name: tc.channel.Name,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
@@ -142,16 +194,22 @@ func TestUpdateChannelReqValidation(t *testing.T) {
|
||||
func TestViewResourceReqValidation(t *testing.T) {
|
||||
key := uuid.NewV4().String()
|
||||
id := uint64(1)
|
||||
wrongID := uint64(0)
|
||||
|
||||
cases := map[string]struct {
|
||||
id uint64
|
||||
id string
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid resource viewing request": {id: id, key: key, err: nil},
|
||||
"missing token": {id: id, key: "", err: things.ErrUnauthorizedAccess},
|
||||
"invalid resource ID": {id: wrongID, key: key, err: things.ErrNotFound},
|
||||
"valid resource viewing request": {
|
||||
id: strconv.FormatUint(id, 10),
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
id: strconv.FormatUint(id, 10),
|
||||
key: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -163,20 +221,38 @@ func TestViewResourceReqValidation(t *testing.T) {
|
||||
|
||||
func TestListResourcesReqValidation(t *testing.T) {
|
||||
key := uuid.NewV4().String()
|
||||
value := 10
|
||||
value := uint64(10)
|
||||
|
||||
cases := map[string]struct {
|
||||
key string
|
||||
offset int
|
||||
limit int
|
||||
offset uint64
|
||||
limit uint64
|
||||
err error
|
||||
}{
|
||||
"valid listing request": {key: key, offset: value, limit: value, err: nil},
|
||||
"missing token": {key: "", offset: value, limit: value, err: things.ErrUnauthorizedAccess},
|
||||
"negative offset": {key: key, offset: -value, limit: value, err: things.ErrMalformedEntity},
|
||||
"zero limit": {key: key, offset: value, limit: 0, err: things.ErrMalformedEntity},
|
||||
"negative limit": {key: key, offset: value, limit: -value, err: things.ErrMalformedEntity},
|
||||
"too big limit": {key: key, offset: value, limit: 20 * value, err: things.ErrMalformedEntity},
|
||||
"valid listing request": {
|
||||
key: key,
|
||||
offset: value,
|
||||
limit: value,
|
||||
err: nil,
|
||||
},
|
||||
"missing token": {
|
||||
key: "",
|
||||
offset: value,
|
||||
limit: value,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"zero limit": {
|
||||
key: key,
|
||||
offset: value,
|
||||
limit: 0,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
"too big limit": {
|
||||
key: key,
|
||||
offset: value,
|
||||
limit: 20 * value,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -190,3 +266,36 @@ func TestListResourcesReqValidation(t *testing.T) {
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionReqValidation(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
key string
|
||||
chanID string
|
||||
thingID string
|
||||
err error
|
||||
}{
|
||||
"valid key": {
|
||||
key: "valid-key",
|
||||
chanID: "1",
|
||||
thingID: "1",
|
||||
err: nil,
|
||||
},
|
||||
"empty key": {
|
||||
key: "",
|
||||
chanID: "1",
|
||||
thingID: "1",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
req := connectionReq{
|
||||
key: tc.key,
|
||||
chanID: tc.chanID,
|
||||
thingID: tc.thingID,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/things"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -61,7 +60,7 @@ func (res removeRes) Empty() bool {
|
||||
}
|
||||
|
||||
type thingRes struct {
|
||||
id uint64
|
||||
id string
|
||||
created bool
|
||||
}
|
||||
|
||||
@@ -76,7 +75,7 @@ func (res thingRes) Code() int {
|
||||
func (res thingRes) Headers() map[string]string {
|
||||
if res.created {
|
||||
return map[string]string{
|
||||
"Location": fmt.Sprintf("/things/%d", res.id),
|
||||
"Location": fmt.Sprintf("/things/%s", res.id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +87,12 @@ func (res thingRes) Empty() bool {
|
||||
}
|
||||
|
||||
type viewThingRes struct {
|
||||
things.Thing
|
||||
ID string `json:"id"`
|
||||
Owner string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (res viewThingRes) Code() int {
|
||||
@@ -104,7 +108,7 @@ func (res viewThingRes) Empty() bool {
|
||||
}
|
||||
|
||||
type listThingsRes struct {
|
||||
Things []things.Thing `json:"things"`
|
||||
Things []viewThingRes `json:"things"`
|
||||
}
|
||||
|
||||
func (res listThingsRes) Code() int {
|
||||
@@ -120,7 +124,7 @@ func (res listThingsRes) Empty() bool {
|
||||
}
|
||||
|
||||
type channelRes struct {
|
||||
id uint64
|
||||
id string
|
||||
created bool
|
||||
}
|
||||
|
||||
@@ -135,7 +139,7 @@ func (res channelRes) Code() int {
|
||||
func (res channelRes) Headers() map[string]string {
|
||||
if res.created {
|
||||
return map[string]string{
|
||||
"Location": fmt.Sprintf("/channels/%d", res.id),
|
||||
"Location": fmt.Sprintf("/channels/%s", res.id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +151,10 @@ func (res channelRes) Empty() bool {
|
||||
}
|
||||
|
||||
type viewChannelRes struct {
|
||||
things.Channel
|
||||
ID string `json:"id"`
|
||||
Owner string `json:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Things []viewThingRes `json:"connected,omitempty"`
|
||||
}
|
||||
|
||||
func (res viewChannelRes) Code() int {
|
||||
@@ -163,7 +170,7 @@ func (res viewChannelRes) Empty() bool {
|
||||
}
|
||||
|
||||
type listChannelsRes struct {
|
||||
Channels []things.Channel `json:"channels"`
|
||||
Channels []viewChannelRes `json:"channels"`
|
||||
}
|
||||
|
||||
func (res listChannelsRes) Code() int {
|
||||
|
||||
@@ -133,16 +133,11 @@ func decodeThingCreation(_ context.Context, r *http.Request) (interface{}, error
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var thing things.Thing
|
||||
if err := json.NewDecoder(r.Body).Decode(&thing); err != nil {
|
||||
req := addThingReq{key: r.Header.Get("Authorization")}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := addThingReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
thing: thing,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -151,20 +146,12 @@ func decodeThingUpdate(_ context.Context, r *http.Request) (interface{}, error)
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var thing things.Thing
|
||||
if err := json.NewDecoder(r.Body).Decode(&thing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := things.FromString(bone.GetValue(r, "id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := updateThingReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
id: id,
|
||||
thing: thing,
|
||||
key: r.Header.Get("Authorization"),
|
||||
id: bone.GetValue(r, "id"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
@@ -175,16 +162,11 @@ func decodeChannelCreation(_ context.Context, r *http.Request) (interface{}, err
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var channel things.Channel
|
||||
if err := json.NewDecoder(r.Body).Decode(&channel); err != nil {
|
||||
req := createChannelReq{key: r.Header.Get("Authorization")}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := createChannelReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
channel: channel,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -193,34 +175,21 @@ func decodeChannelUpdate(_ context.Context, r *http.Request) (interface{}, error
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var channel things.Channel
|
||||
if err := json.NewDecoder(r.Body).Decode(&channel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := things.FromString(bone.GetValue(r, "id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := updateChannelReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
id: id,
|
||||
channel: channel,
|
||||
key: r.Header.Get("Authorization"),
|
||||
id: bone.GetValue(r, "id"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeView(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := things.FromString(bone.GetValue(r, "id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := viewResourceReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
id: id,
|
||||
id: bone.GetValue(r, "id"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
@@ -231,8 +200,8 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, errInvalidQueryParams
|
||||
}
|
||||
offset := 0
|
||||
limit := 10
|
||||
offset := uint64(0)
|
||||
limit := uint64(10)
|
||||
|
||||
off, lmt := q["offset"], q["limit"]
|
||||
|
||||
@@ -241,18 +210,19 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
}
|
||||
|
||||
if len(off) == 1 {
|
||||
offset, err = strconv.Atoi(off[0])
|
||||
offset, err = strconv.ParseUint(off[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, errInvalidQueryParams
|
||||
}
|
||||
}
|
||||
|
||||
if len(lmt) == 1 {
|
||||
limit, err = strconv.Atoi(lmt[0])
|
||||
limit, err = strconv.ParseUint(lmt[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, errInvalidQueryParams
|
||||
}
|
||||
}
|
||||
|
||||
req := listResourcesReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
offset: offset,
|
||||
@@ -263,20 +233,10 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
}
|
||||
|
||||
func decodeConnection(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
thingID, err := things.FromString(bone.GetValue(r, "thingId"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chanID, err := things.FromString(bone.GetValue(r, "chanId"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := connectionReq{
|
||||
key: r.Header.Get("Authorization"),
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
chanID: bone.GetValue(r, "chanId"),
|
||||
thingID: bone.GetValue(r, "thingId"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
|
||||
@@ -68,7 +68,7 @@ func (lm *loggingMiddleware) ViewThing(key string, id uint64) (thing things.Thin
|
||||
return lm.svc.ViewThing(key, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ListThings(key string, offset, limit int) (things []things.Thing, err error) {
|
||||
func (lm *loggingMiddleware) ListThings(key string, offset, limit uint64) (things []things.Thing, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method list_things for key %s took %s to complete", key, time.Since(begin))
|
||||
if err != nil {
|
||||
@@ -133,7 +133,7 @@ func (lm *loggingMiddleware) ViewChannel(key string, id uint64) (channel things.
|
||||
return lm.svc.ViewChannel(key, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ListChannels(key string, offset, limit int) (channels []things.Channel, err error) {
|
||||
func (lm *loggingMiddleware) ListChannels(key string, offset, limit uint64) (channels []things.Channel, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method list_channels for key %s took %s to complete", key, time.Since(begin))
|
||||
if err != nil {
|
||||
|
||||
@@ -61,7 +61,7 @@ func (ms *metricsMiddleware) ViewThing(key string, id uint64) (things.Thing, err
|
||||
return ms.svc.ViewThing(key, id)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) ListThings(key string, offset, limit int) ([]things.Thing, error) {
|
||||
func (ms *metricsMiddleware) ListThings(key string, offset, limit uint64) ([]things.Thing, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "list_things").Add(1)
|
||||
ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds())
|
||||
@@ -106,7 +106,7 @@ func (ms *metricsMiddleware) ViewChannel(key string, id uint64) (things.Channel,
|
||||
return ms.svc.ViewChannel(key, id)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) ListChannels(key string, offset, limit int) ([]things.Channel, error) {
|
||||
func (ms *metricsMiddleware) ListChannels(key string, offset, limit uint64) ([]things.Channel, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "list_channels").Add(1)
|
||||
ms.latency.With("method", "list_channels").Observe(time.Since(begin).Seconds())
|
||||
|
||||
+5
-5
@@ -10,10 +10,10 @@ package things
|
||||
// Channel represents a Mainflux "communication group". This group contains the
|
||||
// things that can exchange messages between eachother.
|
||||
type Channel struct {
|
||||
ID uint64 `json:"id"`
|
||||
Owner string `json:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Things []Thing `json:"connected,omitempty"`
|
||||
ID uint64
|
||||
Owner string
|
||||
Name string
|
||||
Things []Thing
|
||||
}
|
||||
|
||||
// ChannelRepository specifies a channel persistence API.
|
||||
@@ -32,7 +32,7 @@ type ChannelRepository interface {
|
||||
RetrieveByID(string, uint64) (Channel, error)
|
||||
|
||||
// RetrieveAll retrieves the subset of channels owned by the specified user.
|
||||
RetrieveAll(string, int, int) []Channel
|
||||
RetrieveAll(string, uint64, uint64) []Channel
|
||||
|
||||
// Remove removes the channel having the provided identifier, that is owned
|
||||
// by the specified user.
|
||||
|
||||
+20
-4
@@ -24,10 +24,26 @@ func TestFromString(t *testing.T) {
|
||||
out uint64
|
||||
err error
|
||||
}{
|
||||
"from valid number": {in: big, out: math.MaxUint64, err: nil},
|
||||
"from negative number": {in: "-1", out: 0, err: things.ErrNotFound},
|
||||
"from empty string": {in: "", out: 0, err: things.ErrNotFound},
|
||||
"from arbitrary string": {in: "dummy", out: 0, err: things.ErrNotFound},
|
||||
"from valid number": {
|
||||
in: big,
|
||||
out: math.MaxUint64,
|
||||
err: nil,
|
||||
},
|
||||
"from negative number": {
|
||||
in: "-1",
|
||||
out: 0,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
"from empty string": {
|
||||
in: "",
|
||||
out: 0,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
"from arbitrary string": {
|
||||
in: "dummy",
|
||||
out: 0,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
|
||||
@@ -66,7 +66,7 @@ func (crm *channelRepositoryMock) RetrieveByID(owner string, id uint64) (things.
|
||||
return things.Channel{}, things.ErrNotFound
|
||||
}
|
||||
|
||||
func (crm *channelRepositoryMock) RetrieveAll(owner string, offset, limit int) []things.Channel {
|
||||
func (crm *channelRepositoryMock) RetrieveAll(owner string, offset, limit uint64) []things.Channel {
|
||||
channels := make([]things.Channel, 0)
|
||||
|
||||
if offset < 0 || limit <= 0 {
|
||||
|
||||
@@ -65,7 +65,7 @@ func (trm *thingRepositoryMock) RetrieveByID(owner string, id uint64) (things.Th
|
||||
return things.Thing{}, things.ErrNotFound
|
||||
}
|
||||
|
||||
func (trm *thingRepositoryMock) RetrieveAll(owner string, offset, limit int) []things.Thing {
|
||||
func (trm *thingRepositoryMock) RetrieveAll(owner string, offset, limit uint64) []things.Thing {
|
||||
things := make([]things.Thing, 0)
|
||||
|
||||
if offset < 0 || limit <= 0 {
|
||||
|
||||
@@ -99,7 +99,7 @@ func (cr channelRepository) RetrieveByID(owner string, id uint64) (things.Channe
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (cr channelRepository) RetrieveAll(owner string, offset, limit int) []things.Channel {
|
||||
func (cr channelRepository) RetrieveAll(owner string, offset, limit uint64) []things.Channel {
|
||||
q := `SELECT id, name FROM channels WHERE owner = $1 ORDER BY id LIMIT $2 OFFSET $3`
|
||||
items := []things.Channel{}
|
||||
|
||||
|
||||
@@ -37,38 +37,78 @@ func TestChannelUpdate(t *testing.T) {
|
||||
id, _ := chanRepo.Save(c)
|
||||
c.ID = id
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
channel things.Channel
|
||||
err error
|
||||
}{
|
||||
"update existing channel": {channel: c, err: nil},
|
||||
"update non-existing channel with existing user": {channel: things.Channel{ID: wrongID, Owner: email}, err: things.ErrNotFound},
|
||||
"update existing channel ID with non-existing user": {channel: things.Channel{ID: c.ID, Owner: wrongValue}, err: things.ErrNotFound},
|
||||
"update non-existing channel with non-existing user": {channel: things.Channel{ID: wrongID, Owner: wrongValue}, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "update existing channel",
|
||||
channel: c,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing channel with existing user",
|
||||
channel: things.Channel{ID: wrongID, Owner: email},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update existing channel ID with non-existing user",
|
||||
channel: things.Channel{ID: c.ID, Owner: wrongValue},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing channel with non-existing user",
|
||||
channel: things.Channel{ID: wrongID, Owner: wrongValue},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := chanRepo.Update(tc.channel)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleChannelRetrieval(t *testing.T) {
|
||||
email := "channel-single-retrieval@example.com"
|
||||
chanRepo := postgres.NewChannelRepository(db, testLog)
|
||||
thingRepo := postgres.NewThingRepository(db, testLog)
|
||||
|
||||
c := things.Channel{Owner: email}
|
||||
id, _ := chanRepo.Save(c)
|
||||
c.ID = id
|
||||
th := things.Thing{
|
||||
Owner: email,
|
||||
Key: uuid.New().ID(),
|
||||
}
|
||||
th.ID, _ = thingRepo.Save(th)
|
||||
|
||||
c := things.Channel{
|
||||
Owner: email,
|
||||
Things: []things.Thing{th},
|
||||
}
|
||||
|
||||
c.ID, _ = chanRepo.Save(c)
|
||||
chanRepo.Connect(email, c.ID, th.ID)
|
||||
|
||||
cases := map[string]struct {
|
||||
owner string
|
||||
ID uint64
|
||||
err error
|
||||
}{
|
||||
"retrieve channel with existing user": {owner: c.Owner, ID: c.ID, err: nil},
|
||||
"retrieve channel with existing user, non-existing channel": {owner: c.Owner, ID: wrongID, err: things.ErrNotFound},
|
||||
"retrieve channel with non-existing owner": {owner: wrongValue, ID: c.ID, err: things.ErrNotFound},
|
||||
"retrieve channel with existing user": {
|
||||
owner: c.Owner,
|
||||
ID: c.ID,
|
||||
err: nil,
|
||||
},
|
||||
"retrieve channel with existing user, non-existing channel": {
|
||||
owner: c.Owner,
|
||||
ID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
"retrieve channel with non-existing owner": {
|
||||
owner: wrongValue,
|
||||
ID: c.ID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -81,26 +121,42 @@ func TestMultiChannelRetrieval(t *testing.T) {
|
||||
email := "channel-multi-retrieval@example.com"
|
||||
chanRepo := postgres.NewChannelRepository(db, testLog)
|
||||
|
||||
n := 10
|
||||
n := uint64(10)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := uint64(0); i < n; i++ {
|
||||
c := things.Channel{Owner: email}
|
||||
chanRepo.Save(c)
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
owner string
|
||||
offset int
|
||||
limit int
|
||||
size int
|
||||
offset uint64
|
||||
limit uint64
|
||||
size uint64
|
||||
}{
|
||||
"retrieve all channels with existing owner": {owner: email, offset: 0, limit: n, size: n},
|
||||
"retrieve subset of channels with existing owner": {owner: email, offset: n / 2, limit: n, size: n / 2},
|
||||
"retrieve channels with non-existing owner": {owner: wrongValue, offset: n / 2, limit: n, size: 0},
|
||||
"retrieve all channels with existing owner": {
|
||||
owner: email,
|
||||
offset: 0,
|
||||
limit: n,
|
||||
size: n,
|
||||
},
|
||||
"retrieve subset of channels with existing owner": {
|
||||
owner: email,
|
||||
offset: n / 2,
|
||||
limit: n,
|
||||
size: n / 2,
|
||||
},
|
||||
"retrieve channels with non-existing owner": {
|
||||
owner: wrongValue,
|
||||
offset: n / 2,
|
||||
limit: n,
|
||||
size: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
size := len(chanRepo.RetrieveAll(tc.owner, tc.offset, tc.limit))
|
||||
result := chanRepo.RetrieveAll(tc.owner, tc.offset, tc.limit)
|
||||
size := uint64(len(result))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
|
||||
}
|
||||
}
|
||||
@@ -141,11 +197,41 @@ func TestConnect(t *testing.T) {
|
||||
thingID uint64
|
||||
err error
|
||||
}{
|
||||
{desc: "connect existing user, channel and thing", owner: email, chanID: chanID, thingID: thingID, err: nil},
|
||||
{desc: "connect connected channel and thing", owner: email, chanID: chanID, thingID: thingID, err: nil},
|
||||
{desc: "connect with non-existing user", owner: wrongValue, chanID: chanID, thingID: thingID, err: things.ErrNotFound},
|
||||
{desc: "connect non-existing channel", owner: email, chanID: wrongID, thingID: thingID, err: things.ErrNotFound},
|
||||
{desc: "connect non-existing thing", owner: email, chanID: chanID, thingID: wrongID, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "connect existing user, channel and thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "connect connected channel and thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "connect with non-existing user",
|
||||
owner: wrongValue,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "connect non-existing channel",
|
||||
owner: email,
|
||||
chanID: wrongID,
|
||||
thingID: thingID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "connect non-existing thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -174,11 +260,41 @@ func TestDisconnect(t *testing.T) {
|
||||
thingID uint64
|
||||
err error
|
||||
}{
|
||||
{desc: "disconnect connected thing", owner: email, chanID: chanID, thingID: thingID, err: nil},
|
||||
{desc: "disconnect non-connected thing", owner: email, chanID: chanID, thingID: thingID, err: things.ErrNotFound},
|
||||
{desc: "disconnect non-existing user", owner: wrongValue, chanID: chanID, thingID: thingID, err: things.ErrNotFound},
|
||||
{desc: "disconnect non-existing channel", owner: email, chanID: wrongID, thingID: thingID, err: things.ErrNotFound},
|
||||
{desc: "disconnect non-existing thing", owner: email, chanID: chanID, thingID: wrongID, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "disconnect connected thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-connected thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-existing user",
|
||||
owner: wrongValue,
|
||||
chanID: chanID,
|
||||
thingID: thingID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-existing channel",
|
||||
owner: email,
|
||||
chanID: wrongID,
|
||||
thingID: thingID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-existing thing",
|
||||
owner: email,
|
||||
chanID: chanID,
|
||||
thingID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -205,9 +321,21 @@ func TestHasThing(t *testing.T) {
|
||||
key string
|
||||
hasAccess bool
|
||||
}{
|
||||
"access check for thing that has access": {chanID: chanID, key: thing.Key, hasAccess: true},
|
||||
"access check for thing without access": {chanID: chanID, key: wrongValue, hasAccess: false},
|
||||
"access check for non-existing channel": {chanID: wrongID, key: thing.Key, hasAccess: false},
|
||||
"access check for thing that has access": {
|
||||
chanID: chanID,
|
||||
key: thing.Key,
|
||||
hasAccess: true,
|
||||
},
|
||||
"access check for thing without access": {
|
||||
chanID: chanID,
|
||||
key: wrongValue,
|
||||
hasAccess: false,
|
||||
},
|
||||
"access check for non-existing channel": {
|
||||
chanID: wrongID,
|
||||
key: thing.Key,
|
||||
hasAccess: false,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
|
||||
@@ -27,8 +27,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
testLog = logger.New(os.Stdout)
|
||||
db *sql.DB
|
||||
testLog, _ = logger.New(os.Stdout, logger.Info.String())
|
||||
db *sql.DB
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -51,7 +51,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
if err := pool.Retry(func() error {
|
||||
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
|
||||
db, err := sql.Open("postgres", url)
|
||||
db, err = sql.Open("postgres", url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ func (tr thingRepository) RetrieveByKey(key string) (uint64, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (tr thingRepository) RetrieveAll(owner string, offset, limit int) []things.Thing {
|
||||
func (tr thingRepository) RetrieveAll(owner string, offset, limit uint64) []things.Thing {
|
||||
q := `SELECT id, name, type, key, metadata FROM things WHERE owner = $1 ORDER BY id LIMIT $2 OFFSET $3`
|
||||
items := []things.Thing{}
|
||||
|
||||
|
||||
@@ -44,19 +44,36 @@ func TestThingUpdate(t *testing.T) {
|
||||
id, _ := thingRepo.Save(thing)
|
||||
thing.ID = id
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
thing things.Thing
|
||||
err error
|
||||
}{
|
||||
"update existing thing": {thing: thing, err: nil},
|
||||
"update non-existing thing with existing user": {thing: things.Thing{ID: wrongID, Owner: email}, err: things.ErrNotFound},
|
||||
"update existing thing ID with non-existing user": {thing: things.Thing{ID: id, Owner: wrongValue}, err: things.ErrNotFound},
|
||||
"update non-existing thing with non-existing user": {thing: things.Thing{ID: wrongID, Owner: wrongValue}, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "update existing thing",
|
||||
thing: thing,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing thing with existing user",
|
||||
thing: things.Thing{ID: wrongID, Owner: email},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update existing thing ID with non-existing user",
|
||||
thing: things.Thing{ID: id, Owner: wrongValue},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing thing with non-existing user",
|
||||
thing: things.Thing{ID: wrongID, Owner: wrongValue},
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := thingRepo.Update(tc.thing)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +94,21 @@ func TestSingleThingRetrieval(t *testing.T) {
|
||||
ID uint64
|
||||
err error
|
||||
}{
|
||||
"retrieve thing with existing user": {owner: thing.Owner, ID: thing.ID, err: nil},
|
||||
"retrieve non-existing thing with existing user": {owner: thing.Owner, ID: wrongID, err: things.ErrNotFound},
|
||||
"retrieve thing with non-existing owner": {owner: wrongValue, ID: thing.ID, err: things.ErrNotFound},
|
||||
"retrieve thing with existing user": {
|
||||
owner: thing.Owner,
|
||||
ID: thing.ID,
|
||||
err: nil,
|
||||
},
|
||||
"retrieve non-existing thing with existing user": {
|
||||
owner: thing.Owner,
|
||||
ID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
"retrieve thing with non-existing owner": {
|
||||
owner: wrongValue,
|
||||
ID: thing.ID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -105,8 +134,16 @@ func TestThingRetrieveByKey(t *testing.T) {
|
||||
ID uint64
|
||||
err error
|
||||
}{
|
||||
"retrieve existing thing by key": {key: thing.Key, ID: thing.ID, err: nil},
|
||||
"retrieve non-existent thing by key": {key: wrongValue, ID: wrongID, err: things.ErrNotFound},
|
||||
"retrieve existing thing by key": {
|
||||
key: thing.Key,
|
||||
ID: thing.ID,
|
||||
err: nil,
|
||||
},
|
||||
"retrieve non-existent thing by key": {
|
||||
key: wrongValue,
|
||||
ID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -121,9 +158,9 @@ func TestMultiThingRetrieval(t *testing.T) {
|
||||
idp := uuid.New()
|
||||
thingRepo := postgres.NewThingRepository(db, testLog)
|
||||
|
||||
n := 10
|
||||
n := uint64(10)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := uint64(0); i < n; i++ {
|
||||
t := things.Thing{
|
||||
Owner: email,
|
||||
Key: idp.ID(),
|
||||
@@ -134,18 +171,34 @@ func TestMultiThingRetrieval(t *testing.T) {
|
||||
|
||||
cases := map[string]struct {
|
||||
owner string
|
||||
offset int
|
||||
limit int
|
||||
size int
|
||||
offset uint64
|
||||
limit uint64
|
||||
size uint64
|
||||
}{
|
||||
"retrieve all things with existing owner": {owner: email, offset: 0, limit: n, size: n},
|
||||
"retrieve subset of things with existing owner": {owner: email, offset: n / 2, limit: n, size: n / 2},
|
||||
"retrieve things with non-existing owner": {owner: wrongValue, offset: 0, limit: n, size: 0},
|
||||
"retrieve all things with existing owner": {
|
||||
owner: email,
|
||||
offset: 0,
|
||||
limit: n,
|
||||
size: n,
|
||||
},
|
||||
"retrieve subset of things with existing owner": {
|
||||
owner: email,
|
||||
offset: n / 2,
|
||||
limit: n,
|
||||
size: n / 2,
|
||||
},
|
||||
"retrieve things with non-existing owner": {
|
||||
owner: wrongValue,
|
||||
offset: 0,
|
||||
limit: n,
|
||||
size: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
n := len(thingRepo.RetrieveAll(tc.owner, tc.offset, tc.limit))
|
||||
assert.Equal(t, tc.size, n, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, n))
|
||||
result := thingRepo.RetrieveAll(tc.owner, tc.offset, tc.limit)
|
||||
size := uint64(len(result))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux/things/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
channelCache := redis.NewChannelCache(cacheClient)
|
||||
|
||||
cid := uint64(123)
|
||||
tid := uint64(321)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cid uint64
|
||||
tid uint64
|
||||
}{
|
||||
{
|
||||
desc: "connect thing to channel",
|
||||
cid: cid,
|
||||
tid: tid,
|
||||
},
|
||||
{
|
||||
desc: "connect already connected thing to channel",
|
||||
cid: cid,
|
||||
tid: tid,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
err := channelCache.Connect(cid, tid)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: fail to connect due to: %s\n", tc.desc, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasThing(t *testing.T) {
|
||||
channelCache := redis.NewChannelCache(cacheClient)
|
||||
|
||||
cid := uint64(123)
|
||||
tid := uint64(321)
|
||||
|
||||
err := channelCache.Connect(cid, tid)
|
||||
require.Nil(t, err, fmt.Sprintf("connect thing to channel: fail to connect due to: %s\n", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
cid uint64
|
||||
tid uint64
|
||||
hasAccess bool
|
||||
}{
|
||||
"access check for thing that has access": {
|
||||
cid: cid,
|
||||
tid: tid,
|
||||
hasAccess: true,
|
||||
},
|
||||
"access check for thing without access": {
|
||||
cid: cid,
|
||||
tid: cid,
|
||||
hasAccess: false,
|
||||
},
|
||||
"access check for non-existing channel": {
|
||||
cid: tid,
|
||||
tid: tid,
|
||||
hasAccess: false,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
hasAccess := channelCache.HasThing(tc.cid, tc.tid)
|
||||
assert.Equal(t, tc.hasAccess, hasAccess, fmt.Sprintf("%s: expected %t got %t\n", desc, tc.hasAccess, hasAccess))
|
||||
}
|
||||
}
|
||||
func TestDisconnect(t *testing.T) {
|
||||
channelCache := redis.NewChannelCache(cacheClient)
|
||||
|
||||
cid := uint64(123)
|
||||
tid := uint64(321)
|
||||
tid2 := uint64(322)
|
||||
|
||||
err := channelCache.Connect(cid, tid)
|
||||
require.Nil(t, err, fmt.Sprintf("connect thing to channel: fail to connect due to: %s\n", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cid uint64
|
||||
tid uint64
|
||||
hasAccess bool
|
||||
}{
|
||||
{
|
||||
desc: "disconnecting connected thing",
|
||||
cid: cid,
|
||||
tid: tid,
|
||||
hasAccess: false,
|
||||
},
|
||||
{
|
||||
desc: "disconnecting non-connected thing",
|
||||
cid: cid,
|
||||
tid: tid2,
|
||||
hasAccess: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
err := channelCache.Disconnect(tc.cid, tc.tid)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: fail due to: %s\n", tc.desc, err))
|
||||
|
||||
hasAccess := channelCache.HasThing(tc.cid, tc.tid)
|
||||
assert.Equal(t, tc.hasAccess, hasAccess, fmt.Sprintf("access check after %s: expected %t got %t\n", tc.desc, tc.hasAccess, hasAccess))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
channelCache := redis.NewChannelCache(cacheClient)
|
||||
|
||||
cid := uint64(123)
|
||||
cid2 := uint64(124)
|
||||
tid := uint64(321)
|
||||
|
||||
err := channelCache.Connect(cid, tid)
|
||||
require.Nil(t, err, fmt.Sprintf("connect thing to channel: fail to connect due to: %s\n", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cid uint64
|
||||
tid uint64
|
||||
err error
|
||||
hasAccess bool
|
||||
}{
|
||||
{
|
||||
desc: "Remove channel from cache",
|
||||
cid: cid,
|
||||
tid: tid,
|
||||
err: nil,
|
||||
hasAccess: false,
|
||||
},
|
||||
{
|
||||
desc: "Remove non-cached channel from cache",
|
||||
cid: cid2,
|
||||
tid: tid,
|
||||
err: nil,
|
||||
hasAccess: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := channelCache.Remove(tc.cid)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
hasAcces := channelCache.HasThing(tc.cid, tc.tid)
|
||||
assert.Equal(t, tc.hasAccess, hasAcces, "%s - check access after removing channel: expected %t got %t\n", tc.desc, tc.hasAccess, hasAcces)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
dockertest "gopkg.in/ory-am/dockertest.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
wrongID = 0
|
||||
wrongValue = "wrong-value"
|
||||
)
|
||||
|
||||
var cacheClient *redis.Client
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
container, err := pool.Run("redis", "4.0.9-alpine", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start container: %s", err)
|
||||
}
|
||||
|
||||
// When you're done, kill and remove the container
|
||||
defer pool.Purge(container)
|
||||
|
||||
if err := pool.Retry(func() error {
|
||||
cacheClient = redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("localhost:%s", container.GetPort("6379/tcp")),
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
return cacheClient.Ping().Err()
|
||||
}); err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
r "github.com/go-redis/redis"
|
||||
"github.com/mainflux/mainflux/things/redis"
|
||||
"github.com/mainflux/mainflux/things/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestThingSave(t *testing.T) {
|
||||
|
||||
thingCache := redis.NewThingCache(cacheClient)
|
||||
key := uuid.New().ID()
|
||||
id := uint64(123)
|
||||
id2 := uint64(124)
|
||||
|
||||
err := thingCache.Save(key, id2)
|
||||
require.Nil(t, err, fmt.Sprintf("Save thing to cache: expected nil got %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
ID uint64
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "Save thing to cache",
|
||||
ID: id,
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "Save already cached thing to cache",
|
||||
ID: id2,
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := thingCache.Save(tc.key, tc.ID)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestThingID(t *testing.T) {
|
||||
thingCache := redis.NewThingCache(cacheClient)
|
||||
|
||||
key := uuid.New().ID()
|
||||
id := uint64(123)
|
||||
err := thingCache.Save(key, id)
|
||||
require.Nil(t, err, fmt.Sprintf("Save thing to cache: expected nil got %s", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
ID uint64
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"Get ID by existing thing-key": {
|
||||
ID: id,
|
||||
key: key,
|
||||
err: nil,
|
||||
},
|
||||
"Get ID by non-existing thing-key": {
|
||||
ID: 0,
|
||||
key: wrongValue,
|
||||
err: r.Nil,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
cacheID, err := thingCache.ID(tc.key)
|
||||
assert.Equal(t, tc.ID, cacheID, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.ID, cacheID))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestThingRemove(t *testing.T) {
|
||||
thingCache := redis.NewThingCache(cacheClient)
|
||||
|
||||
key := uuid.New().ID()
|
||||
id := uint64(123)
|
||||
id2 := uint64(321)
|
||||
thingCache.Save(key, id)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
ID uint64
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "Remove existing thing from cache",
|
||||
ID: id,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "Remove non-existing thing from cache",
|
||||
ID: id2,
|
||||
err: r.Nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := thingCache.Remove(tc.ID)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
|
||||
}
|
||||
+12
-4
@@ -48,7 +48,7 @@ type Service interface {
|
||||
|
||||
// ListThings retrieves data about subset of things that belongs to the
|
||||
// user identified by the provided key.
|
||||
ListThings(string, int, int) ([]Thing, error)
|
||||
ListThings(string, uint64, uint64) ([]Thing, error)
|
||||
|
||||
// RemoveThing removes the thing identified with the provided ID, that
|
||||
// belongs to the user identified by the provided key.
|
||||
@@ -67,7 +67,7 @@ type Service interface {
|
||||
|
||||
// ListChannels retrieves data about subset of channels that belongs to the
|
||||
// user identified by the provided key.
|
||||
ListChannels(string, int, int) ([]Channel, error)
|
||||
ListChannels(string, uint64, uint64) ([]Channel, error)
|
||||
|
||||
// RemoveChannel removes the thing identified by the provided ID, that
|
||||
// belongs to the user identified by the provided key.
|
||||
@@ -112,6 +112,10 @@ func New(users mainflux.UsersServiceClient, things ThingRepository, channels Cha
|
||||
}
|
||||
|
||||
func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) {
|
||||
if err := thing.Validate(); err != nil {
|
||||
return Thing{}, ErrMalformedEntity
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -133,6 +137,10 @@ func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) {
|
||||
}
|
||||
|
||||
func (ts *thingsService) UpdateThing(key string, thing Thing) error {
|
||||
if err := thing.Validate(); err != nil {
|
||||
return ErrMalformedEntity
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -158,7 +166,7 @@ func (ts *thingsService) ViewThing(key string, id uint64) (Thing, error) {
|
||||
return ts.things.RetrieveByID(res.GetValue(), id)
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListThings(key string, offset, limit int) ([]Thing, error) {
|
||||
func (ts *thingsService) ListThings(key string, offset, limit uint64) ([]Thing, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -228,7 +236,7 @@ func (ts *thingsService) ViewChannel(key string, id uint64) (Channel, error) {
|
||||
return ts.channels.RetrieveByID(res.GetValue(), id)
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListChannels(key string, offset, limit int) ([]Channel, error) {
|
||||
func (ts *thingsService) ListChannels(key string, offset, limit uint64) ([]Channel, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
||||
+374
-94
@@ -42,19 +42,41 @@ func newService(tokens map[string]string) things.Service {
|
||||
func TestAddThing(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
thing things.Thing
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"add new app": {thing: things.Thing{Type: "app", Name: "a"}, key: token, err: nil},
|
||||
"add new device": {thing: things.Thing{Type: "device", Name: "b"}, key: token, err: nil},
|
||||
"add thing with wrong credentials": {thing: things.Thing{Type: "app", Name: "d"}, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
{
|
||||
desc: "add new app",
|
||||
thing: things.Thing{Type: "app", Name: "a"},
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "add new device",
|
||||
thing: things.Thing{Type: "device", Name: "b"},
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "add thing with wrong credentials",
|
||||
thing: things.Thing{Type: "app", Name: "d"},
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "add thing with invalid type",
|
||||
thing: things.Thing{Type: "invalid", Name: "d"},
|
||||
key: wrongValue,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
_, err := svc.AddThing(tc.key, tc.thing)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,19 +85,41 @@ func TestUpdateThing(t *testing.T) {
|
||||
saved, _ := svc.AddThing(token, thing)
|
||||
other := things.Thing{ID: wrongID, Type: "app", Key: "x"}
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
thing things.Thing
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"update existing thing": {thing: saved, key: token, err: nil},
|
||||
"update thing with wrong credentials": {thing: saved, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"update non-existing thing": {thing: other, key: token, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "update existing thing",
|
||||
thing: saved,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update thing with wrong credentials",
|
||||
thing: saved,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing thing",
|
||||
thing: other,
|
||||
key: token,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "update thing with invalid type",
|
||||
thing: things.Thing{Type: "invalid", Name: "d"},
|
||||
key: wrongValue,
|
||||
err: things.ErrMalformedEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := svc.UpdateThing(tc.key, tc.thing)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +132,21 @@ func TestViewThing(t *testing.T) {
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"view existing thing": {id: saved.ID, key: token, err: nil},
|
||||
"view thing with wrong credentials": {id: saved.ID, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"view non-existing thing": {id: wrongID, key: token, err: things.ErrNotFound},
|
||||
"view existing thing": {
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
"view thing with wrong credentials": {
|
||||
id: saved.ID,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"view non-existing thing": {
|
||||
id: wrongID,
|
||||
key: token,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -102,31 +158,65 @@ func TestViewThing(t *testing.T) {
|
||||
func TestListThings(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
|
||||
n := 10
|
||||
for i := 0; i < n; i++ {
|
||||
n := uint64(10)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
svc.AddThing(token, thing)
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
key string
|
||||
offset int
|
||||
limit int
|
||||
size int
|
||||
offset uint64
|
||||
limit uint64
|
||||
size uint64
|
||||
err error
|
||||
}{
|
||||
"list all things": {key: token, offset: 0, limit: n, size: n, err: nil},
|
||||
"list half": {key: token, offset: n / 2, limit: n, size: n / 2, err: nil},
|
||||
"list last thing": {key: token, offset: n - 1, limit: n, size: 1, err: nil},
|
||||
"list empty set": {key: token, offset: n + 1, limit: n, size: 0, err: nil},
|
||||
"list with negative offset": {key: token, offset: -1, limit: n, size: 0, err: nil},
|
||||
"list with negative limit": {key: token, offset: 1, limit: -n, size: 0, err: nil},
|
||||
"list with zero limit": {key: token, offset: 1, limit: 0, size: 0, err: nil},
|
||||
"list with wrong credentials": {key: wrongValue, offset: 0, limit: 0, size: 0, err: things.ErrUnauthorizedAccess},
|
||||
"list all things": {
|
||||
key: token,
|
||||
offset: 0,
|
||||
limit: n,
|
||||
size: n,
|
||||
err: nil,
|
||||
},
|
||||
"list half": {
|
||||
key: token,
|
||||
offset: n / 2,
|
||||
limit: n,
|
||||
size: n / 2,
|
||||
err: nil,
|
||||
},
|
||||
"list last thing": {
|
||||
key: token,
|
||||
offset: n - 1,
|
||||
limit: n,
|
||||
size: 1,
|
||||
err: nil,
|
||||
},
|
||||
"list empty set": {
|
||||
key: token,
|
||||
offset: n + 1,
|
||||
limit: n,
|
||||
size: 0,
|
||||
err: nil,
|
||||
},
|
||||
"list with zero limit": {
|
||||
key: token,
|
||||
offset: 1,
|
||||
limit: 0,
|
||||
size: 0,
|
||||
err: nil,
|
||||
},
|
||||
"list with wrong credentials": {
|
||||
key: wrongValue,
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
size: 0,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
ts, err := svc.ListThings(tc.key, tc.offset, tc.limit)
|
||||
size := len(ts)
|
||||
size := uint64(len(ts))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
@@ -136,38 +226,70 @@ func TestRemoveThing(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
saved, _ := svc.AddThing(token, thing)
|
||||
|
||||
cases := map[string]struct {
|
||||
id uint64
|
||||
key string
|
||||
err error
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"remove thing with wrong credentials": {id: saved.ID, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"remove existing thing": {id: saved.ID, key: token, err: nil},
|
||||
"remove removed thing": {id: saved.ID, key: token, err: nil},
|
||||
"remove non-existing thing": {id: wrongID, key: token, err: nil},
|
||||
{
|
||||
desc: "remove thing with wrong credentials",
|
||||
id: saved.ID,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "remove existing thing",
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove removed thing",
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove non-existing thing",
|
||||
id: wrongID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := svc.RemoveThing(tc.key, tc.id)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateChannel(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
channel things.Channel
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"create channel": {channel: channel, key: token, err: nil},
|
||||
"create channel with wrong credentials": {channel: channel, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
{
|
||||
desc: "create channel",
|
||||
channel: channel,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "create channel with wrong credentials",
|
||||
channel: channel,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
_, err := svc.CreateChannel(tc.key, tc.channel)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,19 +298,35 @@ func TestUpdateChannel(t *testing.T) {
|
||||
saved, _ := svc.CreateChannel(token, channel)
|
||||
other := things.Channel{ID: wrongID}
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
channel things.Channel
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"update existing channel": {channel: saved, key: token, err: nil},
|
||||
"update channel with wrong credentials": {channel: saved, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"update non-existing channel": {channel: other, key: token, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "update existing channel",
|
||||
channel: saved,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update channel with wrong credentials",
|
||||
channel: saved,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "update non-existing channel",
|
||||
channel: other,
|
||||
key: token,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := svc.UpdateChannel(tc.key, tc.channel)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +339,21 @@ func TestViewChannel(t *testing.T) {
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"view existing channel": {id: saved.ID, key: token, err: nil},
|
||||
"view channel with wrong credentials": {id: saved.ID, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"view non-existing channel": {id: wrongID, key: token, err: things.ErrNotFound},
|
||||
"view existing channel": {
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
"view channel with wrong credentials": {
|
||||
id: saved.ID,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"view non-existing channel": {
|
||||
id: wrongID,
|
||||
key: token,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -215,30 +365,64 @@ func TestViewChannel(t *testing.T) {
|
||||
func TestListChannels(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
|
||||
n := 10
|
||||
for i := 0; i < n; i++ {
|
||||
n := uint64(10)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
svc.CreateChannel(token, channel)
|
||||
}
|
||||
cases := map[string]struct {
|
||||
key string
|
||||
offset int
|
||||
limit int
|
||||
size int
|
||||
offset uint64
|
||||
limit uint64
|
||||
size uint64
|
||||
err error
|
||||
}{
|
||||
"list all channels": {key: token, offset: 0, limit: n, size: n, err: nil},
|
||||
"list half": {key: token, offset: n / 2, limit: n, size: n / 2, err: nil},
|
||||
"list last channel": {key: token, offset: n - 1, limit: n, size: 1, err: nil},
|
||||
"list empty set": {key: token, offset: n + 1, limit: n, size: 0, err: nil},
|
||||
"list with negative offset": {key: token, offset: -1, limit: n, size: 0, err: nil},
|
||||
"list with negative limit": {key: token, offset: 1, limit: -n, size: 0, err: nil},
|
||||
"list with zero limit": {key: token, offset: 1, limit: 0, size: 0, err: nil},
|
||||
"list with wrong credentials": {key: wrongValue, offset: 0, limit: 0, size: 0, err: things.ErrUnauthorizedAccess},
|
||||
"list all channels": {
|
||||
key: token,
|
||||
offset: 0,
|
||||
limit: n,
|
||||
size: n,
|
||||
err: nil,
|
||||
},
|
||||
"list half": {
|
||||
key: token,
|
||||
offset: n / 2,
|
||||
limit: n,
|
||||
size: n / 2,
|
||||
err: nil,
|
||||
},
|
||||
"list last channel": {
|
||||
key: token,
|
||||
offset: n - 1,
|
||||
limit: n,
|
||||
size: 1,
|
||||
err: nil,
|
||||
},
|
||||
"list empty set": {
|
||||
key: token,
|
||||
offset: n + 1,
|
||||
limit: n,
|
||||
size: 0,
|
||||
err: nil,
|
||||
},
|
||||
"list with zero limit": {
|
||||
key: token,
|
||||
offset: 1,
|
||||
limit: 0,
|
||||
size: 0,
|
||||
err: nil,
|
||||
},
|
||||
"list with wrong credentials": {
|
||||
key: wrongValue,
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
size: 0,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
ch, err := svc.ListChannels(tc.key, tc.offset, tc.limit)
|
||||
size := len(ch)
|
||||
size := uint64(len(ch))
|
||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
@@ -248,20 +432,41 @@ func TestRemoveChannel(t *testing.T) {
|
||||
svc := newService(map[string]string{token: email})
|
||||
saved, _ := svc.CreateChannel(token, channel)
|
||||
|
||||
cases := map[string]struct {
|
||||
id uint64
|
||||
key string
|
||||
err error
|
||||
cases := []struct {
|
||||
desc string
|
||||
id uint64
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"remove channel with wrong credentials": {id: saved.ID, key: wrongValue, err: things.ErrUnauthorizedAccess},
|
||||
"remove existing channel": {id: saved.ID, key: token, err: nil},
|
||||
"remove removed channel": {id: saved.ID, key: token, err: nil},
|
||||
"remove non-existing channel": {id: saved.ID, key: token, err: nil},
|
||||
{
|
||||
desc: "remove channel with wrong credentials",
|
||||
id: saved.ID,
|
||||
key: wrongValue,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "remove existing channel",
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove removed channel",
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove non-existing channel",
|
||||
id: saved.ID,
|
||||
key: token,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := svc.RemoveChannel(tc.key, tc.id)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,21 +476,46 @@ func TestConnect(t *testing.T) {
|
||||
sth, _ := svc.AddThing(token, thing)
|
||||
sch, _ := svc.CreateChannel(token, channel)
|
||||
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
desc string
|
||||
key string
|
||||
chanID uint64
|
||||
thingID uint64
|
||||
err error
|
||||
}{
|
||||
"connect thing": {key: token, chanID: sch.ID, thingID: sth.ID, err: nil},
|
||||
"connect thing with wrong credentials": {key: wrongValue, chanID: sch.ID, thingID: sth.ID, err: things.ErrUnauthorizedAccess},
|
||||
"connect thing to non-existing channel": {key: token, chanID: wrongID, thingID: sth.ID, err: things.ErrNotFound},
|
||||
"connect non-existing thing to channel": {key: token, chanID: sch.ID, thingID: wrongID, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "connect thing",
|
||||
key: token,
|
||||
chanID: sch.ID,
|
||||
thingID: sth.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "connect thing with wrong credentials",
|
||||
key: wrongValue,
|
||||
chanID: sch.ID,
|
||||
thingID: sth.ID,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "connect thing to non-existing channel",
|
||||
key: token,
|
||||
chanID: wrongID,
|
||||
thingID: sth.ID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "connect non-existing thing to channel",
|
||||
key: token,
|
||||
chanID: sch.ID,
|
||||
thingID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
for _, tc := range cases {
|
||||
err := svc.Connect(tc.key, tc.chanID, tc.thingID)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,11 +533,41 @@ func TestDisconnect(t *testing.T) {
|
||||
thingID uint64
|
||||
err error
|
||||
}{
|
||||
{desc: "disconnect connected thing", key: token, chanID: sch.ID, thingID: sth.ID, err: nil},
|
||||
{desc: "disconnect disconnected thing", key: token, chanID: sch.ID, thingID: sth.ID, err: things.ErrNotFound},
|
||||
{desc: "disconnect with wrong credentials", key: wrongValue, chanID: sch.ID, thingID: sth.ID, err: things.ErrUnauthorizedAccess},
|
||||
{desc: "disconnect from non-existing channel", key: token, chanID: wrongID, thingID: sth.ID, err: things.ErrNotFound},
|
||||
{desc: "disconnect non-existing thing", key: token, chanID: sch.ID, thingID: wrongID, err: things.ErrNotFound},
|
||||
{
|
||||
desc: "disconnect connected thing",
|
||||
key: token,
|
||||
chanID: sch.ID,
|
||||
thingID: sth.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disconnect disconnected thing",
|
||||
key: token,
|
||||
chanID: sch.ID,
|
||||
thingID: sth.ID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect with wrong credentials",
|
||||
key: wrongValue,
|
||||
chanID: sch.ID,
|
||||
thingID: sth.ID,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "disconnect from non-existing channel",
|
||||
key: token,
|
||||
chanID: wrongID,
|
||||
thingID: sth.ID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "disconnect non-existing thing",
|
||||
key: token,
|
||||
chanID: sch.ID,
|
||||
thingID: wrongID,
|
||||
err: things.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -329,9 +589,21 @@ func TestCanAccess(t *testing.T) {
|
||||
channel uint64
|
||||
err error
|
||||
}{
|
||||
"allowed access": {key: sth.Key, channel: sch.ID, err: nil},
|
||||
"not-connected cannot access": {key: wrongValue, channel: sch.ID, err: things.ErrUnauthorizedAccess},
|
||||
"access to non-existing channel": {key: sth.Key, channel: wrongID, err: things.ErrUnauthorizedAccess},
|
||||
"allowed access": {
|
||||
key: sth.Key,
|
||||
channel: sch.ID,
|
||||
err: nil,
|
||||
},
|
||||
"not-connected cannot access": {
|
||||
key: wrongValue,
|
||||
channel: sch.ID,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"access to non-existing channel": {
|
||||
key: sth.Key,
|
||||
channel: wrongID,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -350,8 +622,16 @@ func TestIdentify(t *testing.T) {
|
||||
id uint64
|
||||
err error
|
||||
}{
|
||||
"identify existing thing": {key: sth.Key, id: sth.ID, err: nil},
|
||||
"identify non-existing thing": {key: wrongValue, id: wrongID, err: things.ErrUnauthorizedAccess},
|
||||
"identify existing thing": {
|
||||
key: sth.Key,
|
||||
id: sth.ID,
|
||||
err: nil,
|
||||
},
|
||||
"identify non-existing thing": {
|
||||
key: wrongValue,
|
||||
id: wrongID,
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
|
||||
+7
-7
@@ -12,12 +12,12 @@ import "strings"
|
||||
// Thing represents a Mainflux thing. Each thing is owned by one user, and
|
||||
// it is assigned with the unique identifier and (temporary) access key.
|
||||
type Thing struct {
|
||||
ID uint64 `json:"id"`
|
||||
Owner string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
ID uint64
|
||||
Owner string
|
||||
Type string
|
||||
Name string
|
||||
Key string
|
||||
Metadata string
|
||||
}
|
||||
|
||||
var thingTypes = map[string]bool{
|
||||
@@ -52,7 +52,7 @@ type ThingRepository interface {
|
||||
RetrieveByKey(string) (uint64, error)
|
||||
|
||||
// RetrieveAll retrieves the subset of things owned by the specified user.
|
||||
RetrieveAll(string, int, int) []Thing
|
||||
RetrieveAll(string, uint64, uint64) []Thing
|
||||
|
||||
// Remove removes the thing having the provided identifier, that is owned
|
||||
// by the specified user.
|
||||
|
||||
+13
-11
@@ -16,16 +16,17 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------------------|------------------------------------------|--------------|
|
||||
| MF_USERS_DB_HOST | Database host address | localhost |
|
||||
| MF_USERS_DB_PORT | Database host port | 5432 |
|
||||
| MF_USERS_DB_USER | Database user | mainflux |
|
||||
| MF_USERS_DB_PASSWORD | Database password | mainflux |
|
||||
| MF_USERS_DB | Name of the database used by the service | users |
|
||||
| MF_USERS_HTTP_PORT | Users service HTTP port | 8180 |
|
||||
| MF_USERS_GRPC_PORT | Users service gRPC port | 8181 |
|
||||
| MF_USERS_SECRET | String used for signing tokens | users |
|
||||
| Variable | Description | Default |
|
||||
|----------------------|-------------------------------------------------|--------------|
|
||||
| MF_USERS_LOG_LEVEL | Log level for Users (debug, info, warn, error) | error |
|
||||
| MF_USERS_DB_HOST | Database host address | localhost |
|
||||
| MF_USERS_DB_PORT | Database host port | 5432 |
|
||||
| MF_USERS_DB_USER | Database user | mainflux |
|
||||
| MF_USERS_DB_PASSWORD | Database password | mainflux |
|
||||
| MF_USERS_DB | Name of the database used by the service | users |
|
||||
| MF_USERS_HTTP_PORT | Users service HTTP port | 8180 |
|
||||
| MF_USERS_GRPC_PORT | Users service gRPC port | 8181 |
|
||||
| MF_USERS_SECRET | String used for signing tokens | users |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -42,6 +43,7 @@ services:
|
||||
ports:
|
||||
- [host machine port]:[configured HTTP port]
|
||||
environment:
|
||||
MF_USERS_LOG_LEVEL: [Users log level]
|
||||
MF_USERS_DB_HOST: [Database host address]
|
||||
MF_USERS_DB_PORT: [Database host port]
|
||||
MF_USERS_DB_USER: [Database user]
|
||||
@@ -67,7 +69,7 @@ make users
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_USERS_DB_HOST=[Database host address] MF_USERS_DB_PORT=[Database host port] MF_USERS_DB_USER=[Database user] MF_USERS_DB_PASS=[Database password] MF_USERS_DB=[Name of the database used by the service] MF_USERS_HTTP_PORT=[Service HTTP port] MF_USERS_GRPC_PORT=[Service gRPC port] MF_USERS_SECRET=[String used for signing tokens] $GOBIN/mainflux-users
|
||||
MF_USERS_LOG_LEVEL=[Users log level] MF_USERS_DB_HOST=[Database host address] MF_USERS_DB_PORT=[Database host port] MF_USERS_DB_USER=[Database user] MF_USERS_DB_PASS=[Database password] MF_USERS_DB=[Name of the database used by the service] MF_USERS_HTTP_PORT=[Service HTTP port] MF_USERS_GRPC_PORT=[Service gRPC port] MF_USERS_SECRET=[String used for signing tokens] $GOBIN/mainflux-users
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -66,7 +66,7 @@ func newService() users.Service {
|
||||
}
|
||||
|
||||
func newServer(svc users.Service) *httptest.Server {
|
||||
logger := log.New(os.Stdout)
|
||||
logger, _:= log.New(os.Stdout, log.Info.String())
|
||||
mux := httpapi.MakeHandler(svc, logger)
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
const version string = "0.5.1"
|
||||
|
||||
type response struct {
|
||||
type VersionInfo struct {
|
||||
Service string `json:"service"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
@@ -22,7 +22,7 @@ type response struct {
|
||||
// Version exposes an HTTP handler for retrieving service version.
|
||||
func Version(service string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
res := response{service, version}
|
||||
res := VersionInfo{service, version}
|
||||
|
||||
data, _ := json.Marshal(res)
|
||||
|
||||
|
||||
@@ -4,7 +4,13 @@ Writers provide an implementation of various `message writers`.
|
||||
Message writers are services that consume normalized (in `SenML` format)
|
||||
Mainflux messages and store them in specific data store.
|
||||
|
||||
Writers are optional services and are treated as a plugins. In order to
|
||||
run writer services, core services must be up and running. For more info
|
||||
on the platform core services with its dependencies, please check out
|
||||
the [Docker Compose][compose] file.
|
||||
|
||||
For an in-depth explanation of the usage of `writers`, as well as thorough
|
||||
understanding of Mainflux, please check out the [official documentation][doc].
|
||||
|
||||
[doc]: http://mainflux.readthedocs.io
|
||||
[compose]: ../docker/docker-compose.yml
|
||||
|
||||
@@ -8,12 +8,13 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|---------------------------------|---------------------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_CASSANDRA_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_CASSANDRA_WRITER_DB_CLUSTER | Cassandra cluster comma separated addresses | 127.0.0.1 |
|
||||
| MF_CASSANDRA_WRITER_DB_KEYSPACE | Cassandra keyspace name | mainflux |
|
||||
| Variable | Description | Default |
|
||||
|---------------------------------|------------------------------------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_CASSANDRA_WRITER_LOG_LEVEL | Log level for Cassandra writer (debug, info, warn, error) | error |
|
||||
| MF_CASSANDRA_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_CASSANDRA_WRITER_DB_CLUSTER | Cassandra cluster comma separated addresses | 127.0.0.1 |
|
||||
| MF_CASSANDRA_WRITER_DB_KEYSPACE | Cassandra keyspace name | mainflux |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -27,6 +28,7 @@ default values.
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_CASSANDRA_WRITER_LOG_LEVEL: [Cassandra writer log level]
|
||||
MF_CASSANDRA_WRITER_PORT: [Service HTTP port]
|
||||
MF_CASSANDRA_WRITER_DB_CLUSTER: [Cassandra cluster comma separated addresses]
|
||||
MF_CASSANDRA_WRITER_DB_KEYSPACE: [Cassandra keyspace name]
|
||||
@@ -50,7 +52,7 @@ make cassandra-writer
|
||||
make install
|
||||
|
||||
# Set the environment variables and run the service
|
||||
MF_NATS_URL=[NATS instance URL] MF_CASSANDRA_WRITER_PORT=[Service HTTP port] MF_CASSANDRA_WRITER_DB_CLUSTER=[Cassandra cluster comma separated addresses] MF_CASSANDRA_WRITER_DB_KEYSPACE=[Cassandra keyspace name] $GOBIN/mainflux-cassandra-writer
|
||||
MF_NATS_URL=[NATS instance URL] MF_CASSANDRA_WRITER_LOG_LEVEL=[Cassandra writer log level] MF_CASSANDRA_WRITER_PORT=[Service HTTP port] MF_CASSANDRA_WRITER_DB_CLUSTER=[Cassandra cluster comma separated addresses] MF_CASSANDRA_WRITER_DB_KEYSPACE=[Cassandra keyspace name] $GOBIN/mainflux-cassandra-writer
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
dockertest "gopkg.in/ory-am/dockertest.v3"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stdout)
|
||||
var logger, _ = log.New(os.Stdout, log.Info.String())
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
|
||||
+20
-14
@@ -8,15 +8,18 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|---------------------------|-----------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_INFLUX_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_INFLUX_WRITER_DB_NAME | InfluxDB database name | mainflux |
|
||||
| MF_INFLUX_WRITER_DB_HOST | InfluxDB host | localhost |
|
||||
| MF_INFLUX_WRITER_DB_PORT | Default port of InfluxDB database | 8086 |
|
||||
| MF_INFLUX_WRITER_DB_USER | Default user of InfluxDB database | mainflux |
|
||||
| MF_INFLUX_WRITER_DB_PASS | Default password of InfluxDB user | mainflux |
|
||||
| Variable | Description | Default |
|
||||
|--------------------------------|-----------------------------------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_INFLUX_WRITER_LOG_LEVEL | Log level for InfluxDB writer (debug, info, warn, error) | error |
|
||||
| MF_INFLUX_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_INFLUX_WRITER_BATCH_SIZE | Size of the writer points batch | 5000 |
|
||||
| MF_INFLUX_WRITER_BATCH_TIMEOUT | Time interval in seconds to flush the batch | 1 second |
|
||||
| MF_INFLUX_WRITER_DB_NAME | InfluxDB database name | mainflux |
|
||||
| MF_INFLUX_WRITER_DB_HOST | InfluxDB host | localhost |
|
||||
| MF_INFLUX_WRITER_DB_PORT | Default port of InfluxDB database | 8086 |
|
||||
| MF_INFLUX_WRITER_DB_USER | Default user of InfluxDB database | mainflux |
|
||||
| MF_INFLUX_WRITER_DB_PASS | Default password of InfluxDB user | mainflux |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -30,7 +33,10 @@ default values.
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_INFLUX_WRITER_LOG_LEVEL: [Influx writer log level]
|
||||
MF_INFLUX_WRITER_PORT: [Service HTTP port]
|
||||
MF_INFLUX_WRITER_BATCH_SIZE: [Size of the writer points batch]
|
||||
MF_INFLUX_WRITER_BATCH_TIMEOUT: [Time interval in seconds to flush the batch]
|
||||
MF_INFLUX_WRITER_DB_NAME: [InfluxDB name]
|
||||
MF_INFLUX_WRITER_DB_HOST: [InfluxDB host]
|
||||
MF_INFLUX_WRITER_DB_PORT: [InfluxDB port]
|
||||
@@ -56,22 +62,22 @@ make influxdb
|
||||
make install
|
||||
|
||||
# Set the environment variables and run the service
|
||||
MF_NATS_URL=[NATS instance URL] MF_INFLUX_WRITER_PORT=[Service HTTP port] MF_INFLUX_WRITER_DB_NAME=[InfluxDB database name] MF_INFLUX_WRITER_DB_HOST=[InfluxDB database host] MF_INFLUX_WRITER_DB_PORT=[InfluxDB database port] MF_INFLUX_WRITER_DB_USER=[InfluxDB admin user] MF_INFLUX_WRITER_DB_PASS=[InfluxDB admin password] $GOBIN/mainflux-influxdb
|
||||
MF_NATS_URL=[NATS instance URL] MF_INFLUX_WRITER_LOG_LEVEL=[Influx writer log level] MF_INFLUX_WRITER_PORT=[Service HTTP port] MF_INFLUX_WRITER_BATCH_SIZE=[Size of the writer points batch] MF_INFLUX_WRITER_BATCH_TIMEOUT=[Time interval in seconds to flush the batch] MF_INFLUX_WRITER_DB_NAME=[InfluxDB database name] MF_INFLUX_WRITER_DB_HOST=[InfluxDB database host] MF_INFLUX_WRITER_DB_PORT=[InfluxDB database port] MF_INFLUX_WRITER_DB_USER=[InfluxDB admin user] MF_INFLUX_WRITER_DB_PASS=[InfluxDB admin password] $GOBIN/mainflux-influxdb
|
||||
|
||||
```
|
||||
|
||||
### Using docker-compose
|
||||
|
||||
This service can be deployed using docker containers.
|
||||
Docker compose file is available in <project_root>/docker/addons/influxdb/docker-compose.yml. Besides database
|
||||
Docker compose file is available in `<project_root>/docker/addons/influxdb-writer/docker-compose.yml`. Besides database
|
||||
and writer service, it contains [Grafana platform](https://grafana.com/) which can be used for database
|
||||
exploration and data visualization and analytics. In order to run all Mainflux core services, as well as mentioned optional ones, execute following command:
|
||||
exploration and data visualization and analytics. In order to run Mainflux InfluxDB writer, execute the following command:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker/docker-compose.yml -f docker/addons/influxdb/docker-compose.yml up -d
|
||||
docker-compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
_Please note that order matters here. You need to start core services before additional ones, i. e. core services compose file needs to be the first param of the command. Since all services need to be in the same network and writer services are dependent of core ones, you need to start all of them using single command._
|
||||
_Please note that you need to start core services before the additional ones._
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
package influxdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
@@ -21,35 +23,89 @@ const pointName = "messages"
|
||||
|
||||
var _ writers.MessageRepository = (*influxRepo)(nil)
|
||||
|
||||
var (
|
||||
errZeroValueSize = errors.New("zero value batch size")
|
||||
errZeroValueTimeout = errors.New("zero value batch timeout")
|
||||
)
|
||||
|
||||
type influxRepo struct {
|
||||
database string
|
||||
client influxdata.Client
|
||||
client influxdata.Client
|
||||
batch []*influxdata.Point
|
||||
batchSize int
|
||||
mu sync.Mutex
|
||||
tick <-chan time.Time
|
||||
cfg influxdata.BatchPointsConfig
|
||||
}
|
||||
|
||||
type fields map[string]interface{}
|
||||
type tags map[string]string
|
||||
|
||||
// New returns new InfluxDB writer.
|
||||
func New(client influxdata.Client, database string) (writers.MessageRepository, error) {
|
||||
return &influxRepo{database, client}, nil
|
||||
func New(client influxdata.Client, database string, batchSize int, batchTimeout time.Duration) (writers.MessageRepository, error) {
|
||||
if batchSize == 0 {
|
||||
return &influxRepo{}, errZeroValueSize
|
||||
}
|
||||
|
||||
if batchTimeout == 0 {
|
||||
return &influxRepo{}, errZeroValueTimeout
|
||||
}
|
||||
|
||||
repo := &influxRepo{
|
||||
client: client,
|
||||
cfg: influxdata.BatchPointsConfig{
|
||||
Database: database,
|
||||
},
|
||||
batchSize: batchSize,
|
||||
batch: []*influxdata.Point{},
|
||||
}
|
||||
|
||||
repo.tick = time.NewTicker(batchTimeout).C
|
||||
go func() {
|
||||
for {
|
||||
<-repo.tick
|
||||
repo.save()
|
||||
}
|
||||
}()
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (repo *influxRepo) Save(msg mainflux.Message) error {
|
||||
bp, err := influxdata.NewBatchPoints(influxdata.BatchPointsConfig{
|
||||
Database: repo.database,
|
||||
})
|
||||
func (repo *influxRepo) save() error {
|
||||
repo.mu.Lock()
|
||||
defer repo.mu.Unlock()
|
||||
bp, err := influxdata.NewBatchPoints(repo.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bp.AddPoints(repo.batch)
|
||||
|
||||
if err := repo.client.Write(bp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// It would be nice to reset ticker at this point, which
|
||||
// implies creating a new ticker and goroutine. It would
|
||||
// introduce unnecessary complexity with no justified benefits.
|
||||
repo.batch = []*influxdata.Point{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *influxRepo) Save(msg mainflux.Message) error {
|
||||
tags, fields := repo.tagsOf(&msg), repo.fieldsOf(&msg)
|
||||
pt, err := influxdata.NewPoint(pointName, tags, fields, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bp.AddPoint(pt)
|
||||
return repo.client.Write(bp)
|
||||
repo.mu.Lock()
|
||||
repo.batch = append(repo.batch, pt)
|
||||
repo.mu.Unlock()
|
||||
|
||||
if len(repo.batch)%repo.batchSize == 0 {
|
||||
return repo.save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *influxRepo) tagsOf(msg *mainflux.Message) tags {
|
||||
@@ -57,6 +113,7 @@ func (repo *influxRepo) tagsOf(msg *mainflux.Message) tags {
|
||||
update := strconv.FormatFloat(msg.UpdateTime, 'f', -1, 64)
|
||||
channel := strconv.FormatUint(msg.Channel, 10)
|
||||
publisher := strconv.FormatUint(msg.Publisher, 10)
|
||||
|
||||
return tags{
|
||||
"Channel": channel,
|
||||
"Publisher": publisher,
|
||||
|
||||
@@ -11,47 +11,33 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/influxdata/influxdb/models"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
writer "github.com/mainflux/mainflux/writers/influxdb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
port string
|
||||
testLog = log.New(os.Stdout)
|
||||
testDB = "test"
|
||||
client influxdata.Client
|
||||
clientCfg = influxdata.HTTPConfig{
|
||||
port string
|
||||
testLog, _ = log.New(os.Stdout, log.Info.String())
|
||||
testDB = "test"
|
||||
saveTimeout = 2 * time.Second
|
||||
saveBatchSize = 20
|
||||
streamsSize = 250
|
||||
client influxdata.Client
|
||||
selectMsgs = fmt.Sprintf("SELECT * FROM test..messages")
|
||||
dropMsgs = fmt.Sprintf("DROP SERIES FROM messages")
|
||||
clientCfg = influxdata.HTTPConfig{
|
||||
Username: "test",
|
||||
Password: "test",
|
||||
}
|
||||
)
|
||||
|
||||
// This is utility function to query the database.
|
||||
func queryDB(cmd string) ([]models.Row, error) {
|
||||
q := influxdata.Query{
|
||||
Command: cmd,
|
||||
Database: testDB,
|
||||
}
|
||||
response, err := client.Query(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error() != nil {
|
||||
return nil, response.Error()
|
||||
}
|
||||
// There is only one query, so only one result and
|
||||
// all data are stored in the same series.
|
||||
return response.Results[0].Series, nil
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
msg := mainflux.Message{
|
||||
msg = mainflux.Message{
|
||||
Channel: 45,
|
||||
Publisher: 2580,
|
||||
Protocol: "http",
|
||||
@@ -66,21 +52,120 @@ func TestSave(t *testing.T) {
|
||||
UpdateTime: 5456565466,
|
||||
Link: "link",
|
||||
}
|
||||
)
|
||||
|
||||
q := fmt.Sprintf("SELECT * FROM test..messages\n")
|
||||
// This is utility function to query the database.
|
||||
func queryDB(cmd string) ([][]interface{}, error) {
|
||||
q := influxdata.Query{
|
||||
Command: cmd,
|
||||
Database: testDB,
|
||||
}
|
||||
response, err := client.Query(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error() != nil {
|
||||
return nil, response.Error()
|
||||
}
|
||||
if len(response.Results[0].Series) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
// There is only one query, so only one result and
|
||||
// all data are stored in the same series.
|
||||
return response.Results[0].Series[0].Values, nil
|
||||
}
|
||||
|
||||
func TestNewWriter(t *testing.T) {
|
||||
client, err := influxdata.NewHTTPClient(clientCfg)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB client expected to succeed: %s.\n", err))
|
||||
|
||||
repo, err := writer.New(client, testDB)
|
||||
cases := []struct {
|
||||
desc string
|
||||
batchSize int
|
||||
err error
|
||||
batchTimeout time.Duration
|
||||
errText string
|
||||
}{
|
||||
{
|
||||
desc: "Create writer with zero value batch size",
|
||||
batchSize: 0,
|
||||
batchTimeout: time.Duration(5 * time.Second),
|
||||
errText: "zero value batch size",
|
||||
},
|
||||
{
|
||||
desc: "Create writer with zero value batch timeout",
|
||||
batchSize: 5,
|
||||
batchTimeout: time.Duration(0 * time.Second),
|
||||
errText: "zero value batch timeout",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := writer.New(client, testDB, tc.batchSize, tc.batchTimeout)
|
||||
assert.Equal(t, tc.errText, err.Error(), fmt.Sprintf("%s expected to have error \"%s\", but got \"%s\"", tc.desc, tc.errText, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
client, err := influxdata.NewHTTPClient(clientCfg)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB client expected to succeed: %s.\n", err))
|
||||
|
||||
// Set batch size to 1 to simulate single point insert.
|
||||
repo, err := writer.New(client, testDB, 1, saveTimeout)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB repo expected to succeed: %s.\n", err))
|
||||
|
||||
err = repo.Save(msg)
|
||||
assert.Nil(t, err, fmt.Sprintf("Save operation expected to succeed: %s.\n", err))
|
||||
// Set batch size to value > 1 to simulate real batch.
|
||||
repo1, err := writer.New(client, testDB, saveBatchSize, saveTimeout)
|
||||
require.Nil(t, err, fmt.Sprintf("Creating new InfluxDB repo expected to succeed: %s.\n", err))
|
||||
|
||||
row, err := queryDB(q)
|
||||
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data count expected to succeed: %s.\n", err))
|
||||
cases := []struct {
|
||||
desc string
|
||||
repo writers.MessageRepository
|
||||
previousMsgs int
|
||||
numOfMsg int
|
||||
expectedSize int
|
||||
isBatch bool
|
||||
}{
|
||||
{
|
||||
desc: "save a single message",
|
||||
repo: repo,
|
||||
numOfMsg: 1,
|
||||
expectedSize: 1,
|
||||
isBatch: false,
|
||||
},
|
||||
{
|
||||
desc: "save a batch of messages",
|
||||
repo: repo1,
|
||||
numOfMsg: streamsSize,
|
||||
expectedSize: streamsSize - (streamsSize % saveBatchSize),
|
||||
isBatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
count := len(row)
|
||||
assert.Equal(t, 1, count, fmt.Sprintf("Expected to have 1 value, found %d instead.\n", count))
|
||||
for _, tc := range cases {
|
||||
// Clean previously saved messages.
|
||||
row, err := queryDB(dropMsgs)
|
||||
require.Nil(t, err, fmt.Sprintf("Cleaning data from InfluxDB expected to succeed: %s.\n", err))
|
||||
|
||||
for i := 0; i < tc.numOfMsg; i++ {
|
||||
err := tc.repo.Save(msg)
|
||||
assert.Nil(t, err, fmt.Sprintf("Save operation expected to succeed: %s.\n", err))
|
||||
}
|
||||
|
||||
row, err = queryDB(selectMsgs)
|
||||
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data expected to succeed: %s.\n", err))
|
||||
|
||||
count := len(row)
|
||||
assert.Equal(t, tc.expectedSize, count, fmt.Sprintf("Expected to have %d messages saved, found %d instead.\n", tc.expectedSize, count))
|
||||
|
||||
if tc.isBatch {
|
||||
// Sleep for `saveBatchTime` to trigger ticker and check if the reset of the data is saved.
|
||||
time.Sleep(saveTimeout)
|
||||
|
||||
row, err = queryDB(selectMsgs)
|
||||
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data count expected to succeed: %s.\n", err))
|
||||
count = len(row)
|
||||
assert.Equal(t, tc.numOfMsg, count, fmt.Sprintf("Expected to have %d messages, found %d instead.\n", tc.numOfMsg, count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|-------------------------|-----------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_MONGO_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_MONGO_WRITER_DB_NAME | Default MongoDB database name | mainflux |
|
||||
| MF_MONGO_WRITER_DB_HOST | Default MongoDB database host | localhost |
|
||||
| MF_MONGO_WRITER_DB_PORT | Default MongoDB database port | 27017 |
|
||||
| Variable | Description | Default |
|
||||
|--------------------------------|--------------------------------|-----------------------|
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_MONGO_WRITER_LOG_LEVEL | Log level for MongoDB writer | error |
|
||||
| MF_MONGO_WRITER_PORT | Service HTTP port | 8180 |
|
||||
| MF_MONGO_WRITER_DB_NAME | Default MongoDB database name | mainflux |
|
||||
| MF_MONGO_WRITER_DB_HOST | Default MongoDB database host | localhost |
|
||||
| MF_MONGO_WRITER_DB_PORT | Default MongoDB database port | 27017 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -30,6 +31,7 @@ default values.
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_MONGO_WRITER_LOG_LEVEL: [MongoDB writer log level]
|
||||
MF_MONGO_WRITER_PORT: [Service HTTP port]
|
||||
MF_MONGO_WRITER_DB_NAME: [MongoDB name]
|
||||
MF_MONGO_WRITER_DB_HOST: [MongoDB host]
|
||||
@@ -54,7 +56,7 @@ make mongodb-writer
|
||||
make install
|
||||
|
||||
# Set the environment variables and run the service
|
||||
MF_NATS_URL=[NATS instance URL] MF_MONGO_WRITER_PORT=[Service HTTP port] MF_MONGO_WRITER_DB_NAME=[MongoDB database name] MF_MONGO_WRITER_DB_HOST=[MongoDB database host] MF_MONGO_WRITER_DB_PORT=[MongoDB database port] $GOBIN/mainflux-mongodb-writer
|
||||
MF_NATS_URL=[NATS instance URL] MF_MONGO_WRITER_LOG_LEVEL=[MongoDB writer log level] MF_MONGO_WRITER_PORT=[Service HTTP port] MF_MONGO_WRITER_DB_NAME=[MongoDB database name] MF_MONGO_WRITER_DB_HOST=[MongoDB database host] MF_MONGO_WRITER_DB_PORT=[MongoDB database port] $GOBIN/mainflux-mongodb-writer
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
var (
|
||||
port string
|
||||
addr string
|
||||
testLog = log.New(os.Stdout)
|
||||
testLog, _ = log.New(os.Stdout, log.Info.String())
|
||||
testDB = "test"
|
||||
collection = "mainflux"
|
||||
db mongo.Database
|
||||
|
||||
+2
-2
@@ -23,14 +23,14 @@ type consumer struct {
|
||||
}
|
||||
|
||||
// Start method starts to consume normalized messages received from NATS.
|
||||
func Start(nc *nats.Conn, logger log.Logger, repo MessageRepository) error {
|
||||
func Start(nc *nats.Conn, repo MessageRepository, queue string, logger log.Logger) error {
|
||||
c := consumer{
|
||||
nc: nc,
|
||||
logger: logger,
|
||||
repo: repo,
|
||||
}
|
||||
|
||||
_, err := nc.Subscribe(mainflux.OutputSenML, c.consume)
|
||||
_, err := nc.QueueSubscribe(mainflux.OutputSenML, queue, c.consume)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -8,11 +8,12 @@ The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------------------|---------------------|-----------------------|
|
||||
| MF_WS_ADAPTER_PORT | Service WS port | 8180 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
| Variable | Description | Default |
|
||||
|---------------------------|-------------------------------|-----------------------|
|
||||
| MF_WS_ADAPTER_LOG_LEVEL | Log level for the WS Adapter | error |
|
||||
| MF_WS_ADAPTER_PORT | Service WS port | 8180 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -31,6 +32,7 @@ services:
|
||||
MF_THINGS_URL: [Things service URL]
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_WS_ADAPTER_PORT: [Service WS port]
|
||||
MF_WS_ADAPTER_LOG_LEVEL: [WS adapter log level]
|
||||
```
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
@@ -48,7 +50,7 @@ make ws
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_WS_ADAPTER_PORT=[Service WS port] $GOBIN/mainflux-ws
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_WS_ADAPTER_PORT=[Service WS port] MF_WS_ADAPTER_LOG_LEVEL=[WS adapter log level] $GOBIN/mainflux-ws
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -43,7 +43,8 @@ func newService() ws.Service {
|
||||
}
|
||||
|
||||
func newHTTPServer(svc ws.Service, tc mainflux.ThingsServiceClient) *httptest.Server {
|
||||
mux := api.MakeHandler(svc, tc, log.New(os.Stdout))
|
||||
logger, _ := log.New(os.Stdout, log.Info.String())
|
||||
mux := api.MakeHandler(svc, tc, logger)
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user