Compare commits

..

19 Commits

Author SHA1 Message Date
hykuan 694d67ada2 fix: add missing ThingsURL back (#440)
Signed-off-by: hykuan <hykuan1126@gmail.com>
2018-11-01 10:20:43 +01:00
Dušan Borovčanin d6755e4a72 MF-374 - Bring back CoAP adapter (#413)
* Bring old CoAP code back

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix channel ID formatting due to type change

Uncomment error handling for authorization.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update CoAP adapter docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add copyright headers

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Remove redundant type declaration

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Add CoAP adapter to the list of services

Add CoAp adapter in Makefile services list and fix corresponding documentation.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Refactor CoAP code

Merge multipe `const` block int single and declare consts before vars.
Un-export notFound handler since there is no need to export it.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Update http version endpoint

This separates CoAP and HTTP APIs.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Refactor CoAP POST method handling

This PR is a part of CoAP adapter refactoring that will simplify adapter implementation.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Refactor CoAP adapter

Change CoAP message handling to simplify adapter implementation.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Add backoff timeout for server ping to client

Update CoAP adapter to provide subset of necessary features from
protocol specification.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Fix leaking locked goroutine

In case of the stopped ticker, its channel is NOT closed, so pinging might be left stuck waiting for the stopped ticker to send a notification.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Format code

Use more meaningful name for Handlers map.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Use and stop ticker from the same goroutine

Stop handler Ticker from ping goroutine rather than the cancel goroutine.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Check if subscription already exists in put method

Fix potential leak of handlers providing check inside of put method.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Use MessageID as Observe option

Since MessageID satisfies observe option behaviour, use Message ID
instead of local timestamp. Remove Thicker from handler and use it on
transport layer.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Use name Observer insted of Handler

Name `Observer` is used in protocol specification, so this naming makes
code more self-documenting.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Add CoAP adapter to docker-compose.yml

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Add copyright headers

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Remove unused constants

Fix service name in startup log message.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Add metrics endpoint

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Refactor code

Config fields from main.go should not be exported; minor style changes.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>

* Update authorization URI-Query option

Use `authorization` value in URI-Query option instead of `key`. This
mimics Authorization header in some other protocols (e.g. HTTP). Please
note that this value can be replaced with simple `auth` to save space,
due to constrained URI-Query option size.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-10-31 18:53:25 +01:00
Drasko DRASKOVIC 9cacf5708b NOISSUE - Add info log-level for development (#427)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-10-24 17:13:29 +02:00
Aleksandar Novaković ad5c66fad2 NOISSUE - Refactor SDK and things service (#420)
* Refactor Mainflux go SDK

Add structures instead of string parameters. Add offset and limit
parameters to things and channels methods. Add better configuration
support.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add new public errors with better error handling

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SDK to use uint instread of string id

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update cli to use new SDK API

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Remove TLS termination from nginx configuration

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SDK documentation and structures

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Refactor things service

Decouple HTTP layer from business logic. Remove ID number validation
check. Remove models from HTTP requests and responses.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Reformat tests for things service

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Increase test coverage for things service

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-10-24 10:21:03 +01:00
Ivan Milošević d89e91143b Fix NATS install instruction (#423)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-10-21 22:47:53 +02:00
Sava R 96bcc4cff4 Update NATS docker image to newer version in docker-compose (#418)
Signed-off-by: Sava Radoš <srados@bitsfactory.com>
2018-10-15 11:05:14 +02:00
Drasko DRASKOVIC a8eb7ae039 MF-354 - Add Go SDK (#357)
* MF-354 - Add Go SDK

This PR adds Go SDK. It also refactors `cli` to use new SDK.

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Use http consts. Add doc.

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Insline const

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add initial SDK tests

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix SDK to return values (not HTTP rsp)

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix CLI and test

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* fix typos, add header

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix doc

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix doc, add comment

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Inline error checks

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix typos

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Inline errs

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix typo

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Change fnc parameter name

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Rename getters to Go standard

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Use struct and interface

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Simplify sdk struct

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix README

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-10-14 16:44:21 +02:00
Ivan Milošević 6eade2c1d0 NOISSUE - Add pull request template (#414)
* Add pull request template

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix formatting in docs

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix link for contributing guidelines

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-10-04 10:04:58 +02:00
Dušan Borovčanin 148c1aca0a MF-404 - InfluxDB batch size and timeout zero values (#411)
* Add zero value check for batch size and timeout

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update InfluxDB writer tests

Test creation of InfluxDB writer with invalid (zero) batch size and timeout.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix config load

Loading batch size and timeout from env variables were missing in loadConfigs method.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2018-10-01 17:36:53 +02:00
Aleksandar Novaković 70517bd907 Update mqtt to mqtt communication to support full protocol (#408)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-09-30 19:00:21 +02:00
Ivan Milošević d5f0d7d225 MF-384 - Add test for Redis cache (#405)
* Add test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* MF-384 - Add test for Redis cache

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add copyright headers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix test setup

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* handling errors and put test cases in maps instead of slices

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test case if thing already exists

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix TestThingSave to use require instead of assert

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add test case for removing non-existing thing from cache

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use table of test cases for Remove and test Connect for every case

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use table of test cases for Save

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Test cases in slice instead of map for testing Remove

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Make test-cases independent, use asserts instead of requires

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use slice and map where appropriate

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-09-30 02:03:00 +02:00
Michael Finley b20a846071 Fixing level_test.go (#406)
Signed-off-by: Michael Finley <Michael.Finley@target.com>
2018-09-26 20:22:26 +02:00
Michael Finley 6600d26ef1 MF-379 - Log Level Option (#402)
* Adding an option to select log level

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* making error default log level

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* removing go-kit/levels and expanding levels wrapper

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* refactoring test cases and using log.fatal

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* logger.new no longer accpets enum and now accepts string for level

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* level_test.go refactor to compare error

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* Updating the ws README

Signed-off-by: Michael Finley <Michael.Finley@target.com>

* Adding log level for mqtt Adapter

Signed-off-by: Michael Finley <Michael.Finley@target.com>
2018-09-26 18:58:51 +02:00
Dušan Borovčanin 3de34062db NOISSUE - Influxdb batch add (#394)
* Add batch of streams

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add env variables for batch point setup

Fix InfluxDB reader tests.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Compose and Kubernetes config

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update env variables

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused const

Refactor code and docs.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Handle NewBatchPoints error

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Reduce wait time in  batch save test

Fix typos.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update tests

Use single test with multiple cases to test single point save as well as
a batch of points.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add an explanation for not resetting ticker

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2018-09-23 01:53:03 +02:00
Ivan Milošević 8e9d7b4fdb MF-389 - Add documentation for Writers and Readers (#398)
* MF-389 - Add documentation for Writers and Readers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* MF-389 - Update readme files for readers and writers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* MF-389 - Update mongodb-reader docker-compose.yml

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix links formatting in docs

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Updated reader and writer docs

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix formatting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Update Grafana access

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-09-20 15:32:50 +02:00
Dušan Borovčanin 5757380a63 NOISSUE - Dev dockers (#392)
* Add development Docker images build

Add Dockerfile.dev and target in the Makefile to provide much faster image
building for `development purposes`.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Build single service in dev mode

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix dev-guide

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2018-09-19 20:20:12 +02:00
Jovan Kostovski c944205080 dev guide spelling and formatting fixes (#393)
Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2018-09-17 21:32:13 +02:00
Drasko DRASKOVIC d825174ccb NOISSUE - Separate docker-push into function (#386)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-09-13 15:40:40 +02:00
Aleksandar Novaković 6408b8a9d3 Fix NATS subscription bug in writers (#391)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-09-13 10:06:34 +02:00
132 changed files with 6661 additions and 3726 deletions
+15
View File
@@ -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
Generated
+207 -23
View File
@@ -3,92 +3,137 @@
[[projects]]
branch = "master"
digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm"
"winterm",
]
pruneopts = "UT"
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6"
name = "github.com/Microsoft/go-winio"
packages = ["."]
pruneopts = "UT"
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7"
[[projects]]
branch = "master"
digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae"
name = "github.com/Nvveen/Gotty"
packages = ["."]
pruneopts = "UT"
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
[[projects]]
digest = "1:320e7ead93de9fd2b0e59b50fd92a4d50c1f8ab455d96bc2eb083267453a9709"
name = "github.com/asaskevich/govalidator"
packages = ["."]
pruneopts = "UT"
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
branch = "master"
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
branch = "master"
digest = "1:35ac403a135b8a138870950720be1f0399e0a48387d4b5cca841b0ed8cf7a40b"
name = "github.com/buger/jsonparser"
packages = ["."]
pruneopts = "UT"
revision = "2cac668e8456b4284edb0715e17e2af02d3ec993"
[[projects]]
digest = "1:2209584c0f7c9b68c23374e659357ab546e1b70eec2761f03280f69a8fd23d77"
name = "github.com/cenkalti/backoff"
packages = ["."]
pruneopts = "UT"
revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e"
version = "v2.0.0"
[[projects]]
branch = "master"
digest = "1:24310b2d7f85873dde28d21687a40f7eba239970827c6a5d68cb529c9803de3a"
name = "github.com/cisco/senml"
packages = ["."]
pruneopts = "UT"
revision = "448c9510575e1dd6f780cb10addbad998cf80418"
[[projects]]
branch = "master"
digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115"
name = "github.com/containerd/continuity"
packages = ["pathdriver"]
pruneopts = "UT"
revision = "c6cef34830231743494fe2969284df7b82cc0ad0"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:ca71cdedcb487a361fe2588afeb3d373c0dfe21feaac229270fd6de234bd3c5e"
name = "github.com/dereulenspiegel/coap-mux"
packages = ["."]
pruneopts = "UT"
revision = "e137e310a99955be01d60b2237da89153ea40987"
[[projects]]
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:87dcb59127512b84097086504c16595cf8fef35b9e0bfca565dfc06e198158d7"
name = "github.com/docker/go-connections"
packages = ["nat"]
pruneopts = "UT"
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
version = "v0.3.0"
[[projects]]
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
name = "github.com/docker/go-units"
packages = ["."]
pruneopts = "UT"
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
version = "v0.3.3"
[[projects]]
branch = "master"
digest = "1:f77ee3cd73cbf60434b4f72ec46a66773c555d46764399735b5f9a28505f3ece"
name = "github.com/dustin/go-coap"
packages = ["."]
pruneopts = "UT"
revision = "ddcc80675fa42611359d91a6dfa5aa57fb90e72b"
[[projects]]
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "UT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:3fd00c9248f27f544a9bff7254adf110e1c791d067522025fcaac7528b55087a"
name = "github.com/go-kit/kit"
packages = [
"endpoint",
@@ -97,18 +142,22 @@
"metrics/internal/lv",
"metrics/prometheus",
"transport/grpc",
"transport/http"
"transport/http",
]
pruneopts = "UT"
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
version = "v0.6.0"
[[projects]]
digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659"
name = "github.com/go-logfmt/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
digest = "1:7c2fd446293ff7799cc496d3446e674ee67902d119f244de645caf95dff1bb98"
name = "github.com/go-redis/redis"
packages = [
".",
@@ -118,125 +167,159 @@
"internal/pool",
"internal/proto",
"internal/singleflight",
"internal/util"
"internal/util",
]
pruneopts = "UT"
revision = "f3bba01df2026fc865f7782948845db9cf44cf23"
version = "v6.14.1"
[[projects]]
digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "UT"
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
digest = "1:8dfa0b1b7c4b7398d46340aef0f2a58aad5a75959eeeb79e023ab826f0c7f7f0"
name = "github.com/go-zoo/bone"
packages = ["."]
pruneopts = "UT"
revision = "fd0aebc74e908868b09ac140fb5a53cb363884c1"
version = "1.2"
[[projects]]
branch = "master"
digest = "1:cf497f8273ddb581939f686791154c7d349322f01a8cade02f21504c036abc5f"
name = "github.com/gocql/gocql"
packages = [
".",
"internal/lru",
"internal/murmur",
"internal/streams"
"internal/streams",
]
pruneopts = "UT"
revision = "651d6b1f343c6e168e45d119dcfe8f71e5bafb32"
[[projects]]
digest = "1:9a688317f3231e0175b3429033f44411906c0ce119361b7b5019d01375f8cff7"
name = "github.com/gogo/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
"ptypes/timestamp",
]
pruneopts = "UT"
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:364b908b9b27b97ab838f2f6f1b1f46281fa29b978a037d72a9b1d4f6d940190"
name = "github.com/hailocab/go-hostpool"
packages = ["."]
pruneopts = "UT"
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
[[projects]]
branch = "master"
digest = "1:51a6dbf048dc420793ec106b7c8d151d1ffb119285ce044cc41d2dbb7e23732e"
name = "github.com/hokaccha/go-prettyjson"
packages = ["."]
pruneopts = "UT"
revision = "e6b9231a2b1cc5798b336fdbf99c16cd8ad1e0c9"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:ce530152667dc8ba823a586c1c81175aff2c4e15500520883a2727d0ed17c6f1"
name = "github.com/influxdata/influxdb"
packages = [
"client/v2",
"models",
"pkg/escape"
"pkg/escape",
]
pruneopts = "UT"
revision = "02d7d4f043b34ecb4e9b2dbec298c6f9450c2a32"
version = "v1.5.2"
[[projects]]
branch = "master"
digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72"
name = "github.com/kr/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
branch = "master"
digest = "1:1e537dfb7b4d543e25efa2c812084dd65736f8010979c1b7e167a5b876563199"
name = "github.com/lib/pq"
packages = [
".",
"oid"
"oid",
]
pruneopts = "UT"
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
[[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = "UT"
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
digest = "1:e274a8817ced03e0eaab6615aee825e4bd374a31180a3f5c2c640403840cc9dc"
name = "github.com/mongodb/mongo-go-driver"
packages = [
"bson",
@@ -286,52 +369,64 @@
"mongo/mongoopt",
"mongo/replaceopt",
"mongo/runcmdopt",
"mongo/updateopt"
"mongo/updateopt",
]
pruneopts = "UT"
revision = "8df56f107cdd485351eb2523992c3364114b4b2e"
version = "v0.0.10"
[[projects]]
digest = "1:6ec5ca70ff99467ff3b134ca05ae1d247e42a0377a7c27a756ba932f4dfc3a88"
name = "github.com/nats-io/go-nats"
packages = [
".",
"encoders/builtin",
"util"
"util",
]
pruneopts = "UT"
revision = "062418ea1c2181f52dc0f954f6204370519a868b"
version = "v1.5.0"
[[projects]]
digest = "1:c3cd663f2f30b92536b9f290ac85c6310dae36a14cb8961553ae9ccf0d85ae41"
name = "github.com/nats-io/nuid"
packages = ["."]
pruneopts = "UT"
revision = "289cccf02c178dc782430d534e3c1f5b72af807f"
version = "v1.0.0"
[[projects]]
digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d"
name = "github.com/opencontainers/go-digest"
packages = ["."]
pruneopts = "UT"
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6"
name = "github.com/opencontainers/image-spec"
packages = [
"specs-go",
"specs-go/v1"
"specs-go/v1",
]
pruneopts = "UT"
revision = "d60099175f88c47cd379c4738d158884749ed235"
version = "v1.0.1"
[[projects]]
digest = "1:1869683e323ebff2bdf8adcb560f82bf6f8d94019d35099e3403f7df12e9c07e"
name = "github.com/opencontainers/runc"
packages = [
"libcontainer/system",
"libcontainer/user"
"libcontainer/user",
]
pruneopts = "UT"
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
version = "v0.1.1"
[[projects]]
digest = "1:3b830e06971be8448e7c9e57c9cc15ce53ab7e8846e7b7c39110a177c7fae466"
name = "github.com/ory/dockertest"
packages = [
"docker",
@@ -357,126 +452,158 @@
"docker/types/network",
"docker/types/registry",
"docker/types/strslice",
"docker/types/versions"
"docker/types/versions",
]
pruneopts = "UT"
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
version = "v3.3.0"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:d14a5f4bfecf017cb780bdde1b6483e5deb87e12c332544d2c430eda58734bcb"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/promhttp"
"prometheus/promhttp",
]
pruneopts = "UT"
revision = "c5b7fccd204277076155f10851dad72b76a49317"
version = "v0.8.0"
[[projects]]
branch = "master"
digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "UT"
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
"model",
]
pruneopts = "UT"
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
[[projects]]
branch = "master"
digest = "1:570784e0ddbf67f14087c6d8cfb7b999b9c55e75518a755e88cb202566ea8a17"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs"
"xfs",
]
pruneopts = "UT"
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
branch = "master"
digest = "1:c9f218adb858f881ce84d0c3d6ad9f65b40d7f31c705c8f00b04106294cb006e"
name = "github.com/rubenv/sql-migrate"
packages = [
".",
"sqlparse"
"sqlparse",
]
pruneopts = "UT"
revision = "081fe17d19ff4e2dd9f5a0c1158e6bcf74da6906"
[[projects]]
digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925"
name = "github.com/satori/go.uuid"
packages = ["."]
pruneopts = "UT"
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
digest = "1:f7805d1cc7776d00da2aa3dbacf85927e58e6dad630ba9f3ea5b92d719f96269"
name = "github.com/sony/gobreaker"
packages = ["."]
pruneopts = "UT"
revision = "e9556a45379ef1da12e54847edb2fb3d7d566f36"
version = "0.3.0"
[[projects]]
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require"
"require",
]
pruneopts = "UT"
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = "UT"
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:7310f5459b88177d24e26c5ec7deb100b78ec03c0437d7a7aaec3c7eac333a55"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish",
"pbkdf2",
"ssh/terminal"
"ssh/terminal",
]
pruneopts = "UT"
revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226"
[[projects]]
branch = "master"
digest = "1:49131f79ef90ca701638024267ba6e3b5cd0ce387e0b3ec79961a42430873db9"
name = "golang.org/x/net"
packages = [
"context",
@@ -485,26 +612,32 @@
"http2/hpack",
"idna",
"internal/timeseries",
"trace"
"trace",
]
pruneopts = "UT"
revision = "f73e4c9ed3b7ebdd5f699a16a880c2b1994e50dd"
[[projects]]
branch = "master"
digest = "1:e0140c0c868c6e0f01c0380865194592c011fe521d6e12d78bfd33e756fe018a"
name = "golang.org/x/sync"
packages = ["semaphore"]
pruneopts = "UT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:66e34aa65b83061616603ba83517a4523c7261e18afd01c769dd215614bf7d70"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
"windows",
]
pruneopts = "UT"
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
@@ -520,18 +653,22 @@
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:cd018653a358d4b743a9d3bee89e825521f2ab2f2ec0770164bf7632d8d73ab7"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
[[projects]]
digest = "1:bb5ad466f9c22e1d7f34537a408ca215ee192e759979723b2b5e3f26dc80fd1f"
name = "google.golang.org/grpc"
packages = [
".",
@@ -557,32 +694,79 @@
"stats",
"status",
"tap",
"transport"
"transport",
]
pruneopts = "UT"
revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
version = "v1.12.0"
[[projects]]
digest = "1:fa9a7c0ef59217bd22f32eb7cc027894d73f340a5633258cc079dec025db6a7f"
name = "gopkg.in/gorp.v1"
packages = ["."]
pruneopts = "UT"
revision = "c87af80f3cc5036b55b83d77171e156791085e2e"
version = "v1.7.1"
[[projects]]
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
name = "gopkg.in/inf.v0"
packages = ["."]
pruneopts = "UT"
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
digest = "1:784e96fed7cbc35a3fdefb802ed2f841c16dbc3cf6c02f6422cc94ff45d011fc"
name = "gopkg.in/ory-am/dockertest.v3"
packages = ["."]
pruneopts = "UT"
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
version = "v3.3.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "1f18adf44738d8f5c48b2f0576ab90c2f2c549f13a846b8f3c95aabaebe8efb4"
input-imports = [
"github.com/asaskevich/govalidator",
"github.com/cisco/senml",
"github.com/dereulenspiegel/coap-mux",
"github.com/dgrijalva/jwt-go",
"github.com/dustin/go-coap",
"github.com/fatih/color",
"github.com/go-kit/kit/endpoint",
"github.com/go-kit/kit/log",
"github.com/go-kit/kit/metrics",
"github.com/go-kit/kit/metrics/prometheus",
"github.com/go-kit/kit/transport/grpc",
"github.com/go-kit/kit/transport/http",
"github.com/go-redis/redis",
"github.com/go-zoo/bone",
"github.com/gocql/gocql",
"github.com/gogo/protobuf/proto",
"github.com/golang/protobuf/proto",
"github.com/gorilla/websocket",
"github.com/hokaccha/go-prettyjson",
"github.com/influxdata/influxdb/client/v2",
"github.com/lib/pq",
"github.com/mongodb/mongo-go-driver/bson",
"github.com/mongodb/mongo-go-driver/mongo",
"github.com/mongodb/mongo-go-driver/mongo/findopt",
"github.com/nats-io/go-nats",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/rubenv/sql-migrate",
"github.com/satori/go.uuid",
"github.com/sony/gobreaker",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"golang.org/x/crypto/bcrypt",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/status",
"gopkg.in/ory-am/dockertest.v3",
]
solver-name = "gps-cdcl"
solver-version = 1
+8
View File
@@ -86,6 +86,14 @@
branch = "master"
name = "github.com/gocql/gocql"
[[constraint]]
branch = "master"
name = "github.com/dereulenspiegel/coap-mux"
[[constraint]]
branch = "master"
name = "github.com/dustin/go-coap"
[prune]
go-tests = true
unused-packages = true
+26 -11
View File
@@ -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
SERVICES = users things http normalizer ws coap 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
+15 -7
View File
@@ -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"))
+19 -10
View File
@@ -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
View File
@@ -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)
+129
View File
@@ -0,0 +1,129 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
gocoap "github.com/dustin/go-coap"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/coap"
"github.com/mainflux/mainflux/coap/api"
"github.com/mainflux/mainflux/coap/nats"
logger "github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
broker "github.com/nats-io/go-nats"
)
const (
defPort = "5683"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defLogLevel = "error"
envPort = "MF_COAP_ADAPTER_PORT"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_COAP_ADAPTER_LOG_LEVEL"
)
type config struct {
port string
natsURL string
thingsURL string
logLevel string
}
func main() {
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 {
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
os.Exit(1)
}
defer nc.Close()
conn, err := grpc.Dial(cfg.thingsURL, grpc.WithInsecure())
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
os.Exit(1)
}
defer conn.Close()
cc := thingsapi.NewClient(conn)
respChan := make(chan string, 10000)
pubsub := nats.New(nc)
svc := coap.New(pubsub, respChan)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "coap_adapter",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "coap_adapter",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
errs := make(chan error, 2)
go startHTTPServer(cfg.port, logger, errs)
go startCOAPServer(cfg.port, svc, cc, respChan, logger, errs)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("CoAP adapter terminated: %s", err))
}
func loadConfig() config {
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
port: mainflux.Env(envPort, defPort),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
}
}
func startHTTPServer(port string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
logger.Info(fmt.Sprintf("CoAP service started, exposed port %s", port))
errs <- http.ListenAndServe(p, api.MakeHTTPHandler())
}
func startCOAPServer(port string, svc coap.Service, auth mainflux.ThingsServiceClient, respChan chan<- string, l logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
l.Info(fmt.Sprintf("CoAP adapter service started, exposed port %s", port))
errs <- gocoap.ListenAndServe("udp", p, api.MakeCOAPHandler(svc, auth, l, respChan))
}
+21 -7
View File
@@ -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),
}
}
+13 -6
View File
@@ -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
View File
@@ -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())
}
+15 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+19 -8
View File
@@ -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,12 @@ func main() {
err = <-errs
logger.Error(fmt.Sprintf("WebSocket 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),
}
}
+61
View File
@@ -0,0 +1,61 @@
# Mainflux CoAP Adapter
Mainflux CoAP adapter provides an [CoAP](http://coap.technology/) API for sending messages through the
platform.
## Configuration
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_COAP_ADAPTER_PORT | Service listening port | 5683 |
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
| MF_THINGS_URL | Things service URL | localhost:8181 |
| MF_COAP_ADAPTER_LOG_LEVEL | Service log level | error |
## Deployment
The service is distributed as Docker container. The following snippet provides
a compose file template that can be used to deploy the service container locally:
```yaml
version: "2"
services:
adapter:
image: mainflux/coap:[version]
container_name: [instance name]
ports:
- [host machine port]:[configured port]
environment:
MF_COAP_ADAPTER_PORT: [Service HTTP port]
MF_NATS_URL: [NATS instance URL]
MF_THINGS_URL: [Things service URL]
MF_COAP_ADAPTER_LOG_LEVEL: [Service log level]
```
Running this service outside of container requires working instance of the NATS service.
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
go get github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
# compile the http
make coap
# copy binary to bin
make install
# set the environment variables and run the service
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_COAP_ADAPTER_PORT=[Service HTTP port] MF_COAP_ADAPTER_LOG_LEVEL=[Service log level] $GOBIN/mainflux-coap
```
## Usage
Since CoAP protocol does not support `Authorization` header (option), in order to send CoAP messages,
client valid `authorization` value must be present in `Uri-Query` option.
+150
View File
@@ -0,0 +1,150 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package coap contains the domain concept definitions needed to support
// Mainflux coap adapter service functionality. All constant values are taken
// from RFC, and could be adjusted based on specific use case.
package coap
import (
"errors"
"sync"
"time"
"github.com/mainflux/mainflux"
broker "github.com/nats-io/go-nats"
)
const (
chanID = "id"
keyHeader = "key"
// AckRandomFactor is default ACK coefficient.
AckRandomFactor = 1.5
// AckTimeout is the amount of time to wait for a response.
AckTimeout = 2000 * time.Millisecond
// MaxRetransmit is the maximum number of times a message will be retransmitted.
MaxRetransmit = 4
)
var (
errBadOption = errors.New("bad option")
// ErrFailedMessagePublish indicates that message publishing failed.
ErrFailedMessagePublish = errors.New("failed to publish message")
// ErrFailedSubscription indicates that client couldn't subscribe to specified channel.
ErrFailedSubscription = errors.New("failed to subscribe to a channel")
// ErrFailedConnection indicates that service couldn't connect to message broker.
ErrFailedConnection = errors.New("failed to connect to message broker")
)
// Broker represents NATS broker instance.
type Broker interface {
mainflux.MessagePublisher
// Subscribes to channel with specified id and adds subscription to
// service map of subscriptions under given ID.
Subscribe(uint64, string, *Observer) error
}
// Service specifies coap service API.
type Service interface {
Broker
// Unsubscribe method is used to stop observing resource.
Unsubscribe(string)
}
var _ Service = (*adapterService)(nil)
type adapterService struct {
pubsub Broker
obs map[string]*Observer
obsLock sync.Mutex
}
// New instantiates the CoAP adapter implementation.
func New(pubsub Broker, responses <-chan string) Service {
as := &adapterService{
pubsub: pubsub,
obs: make(map[string]*Observer),
obsLock: sync.Mutex{},
}
go as.listenResponses(responses)
return as
}
func (svc *adapterService) get(obsID string) (*Observer, bool) {
svc.obsLock.Lock()
defer svc.obsLock.Unlock()
val, ok := svc.obs[obsID]
return val, ok
}
func (svc *adapterService) put(obsID string, o *Observer) {
svc.obsLock.Lock()
defer svc.obsLock.Unlock()
val, ok := svc.obs[obsID]
if ok {
close(val.Cancel)
}
svc.obs[obsID] = o
}
func (svc *adapterService) remove(obsID string) {
svc.obsLock.Lock()
defer svc.obsLock.Unlock()
val, ok := svc.obs[obsID]
if ok {
close(val.Cancel)
delete(svc.obs, obsID)
}
}
// ListenResponses method handles ACK messages received from client.
func (svc *adapterService) listenResponses(responses <-chan string) {
for {
id := <-responses
val, ok := svc.get(id)
if ok {
val.StoreExpired(false)
}
}
}
func (svc *adapterService) Publish(msg mainflux.RawMessage) error {
if err := svc.pubsub.Publish(msg); err != nil {
switch err {
case broker.ErrConnectionClosed, broker.ErrInvalidConnection:
return ErrFailedConnection
default:
return ErrFailedMessagePublish
}
}
return nil
}
func (svc *adapterService) Subscribe(chanID uint64, obsID string, o *Observer) error {
if err := svc.pubsub.Subscribe(chanID, obsID, o); err != nil {
return ErrFailedSubscription
}
// Put method removes Observer if already exists.
svc.put(obsID, o)
return nil
}
func (svc *adapterService) Unsubscribe(obsID string) {
svc.remove(obsID)
}
+10
View File
@@ -0,0 +1,10 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package api contains API-related concerns: endpoint definitions, middlewares
// and all resource representations.
package api
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
package api
import (
"fmt"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/coap"
log "github.com/mainflux/mainflux/logger"
)
var _ coap.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc coap.Service
}
// LoggingMiddleware adds logging facilities to the adapter.
func LoggingMiddleware(svc coap.Service, logger log.Logger) coap.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Publish(msg mainflux.RawMessage) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method publish to channel %d took %s to complete", msg.Channel, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Publish(msg)
}
func (lm *loggingMiddleware) Subscribe(chanID uint64, obsID string, o *coap.Observer) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method subscribe to channel %d for client %s took %s to complete", chanID, obsID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Subscribe(chanID, obsID, o)
}
func (lm *loggingMiddleware) Unsubscribe(obsID string) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method unsubscribe for the client %s took %s to complete without errors.", obsID, time.Since(begin))
lm.logger.Info(fmt.Sprintf(message))
}(time.Now())
lm.svc.Unsubscribe(obsID)
}
+62
View File
@@ -0,0 +1,62 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
package api
import (
"time"
"github.com/go-kit/kit/metrics"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/coap"
)
var _ coap.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc coap.Service
}
// MetricsMiddleware instruments adapter by tracking request count and latency.
func MetricsMiddleware(svc coap.Service, counter metrics.Counter, latency metrics.Histogram) coap.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (mm *metricsMiddleware) Publish(msg mainflux.RawMessage) error {
defer func(begin time.Time) {
mm.counter.With("method", "publish").Add(1)
mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Publish(msg)
}
func (mm *metricsMiddleware) Subscribe(chanID uint64, clientID string, o *coap.Observer) error {
defer func(begin time.Time) {
mm.counter.With("method", "subscribe").Add(1)
mm.latency.With("method", "subscribe").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Subscribe(chanID, clientID, o)
}
func (mm *metricsMiddleware) Unsubscribe(clientID string) {
defer func(begin time.Time) {
mm.counter.With("method", "unsubscribe").Add(1)
mm.latency.With("method", "unsubscribe").Observe(time.Since(begin).Seconds())
}(time.Now())
mm.svc.Unsubscribe(clientID)
}
+296
View File
@@ -0,0 +1,296 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux/coap"
log "github.com/mainflux/mainflux/logger"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
mux "github.com/dereulenspiegel/coap-mux"
gocoap "github.com/dustin/go-coap"
"github.com/mainflux/mainflux"
)
const protocol = "coap"
var (
errBadRequest = errors.New("bad request")
errBadOption = errors.New("bad option")
auth mainflux.ThingsServiceClient
logger log.Logger
)
type handler func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message
func notFoundHandler(l *net.UDPConn, a *net.UDPAddr, m *gocoap.Message) *gocoap.Message {
if m.IsConfirmable() {
return &gocoap.Message{
Type: gocoap.Acknowledgement,
Code: gocoap.NotFound,
}
}
return nil
}
//MakeHTTPHandler creates handler for version endpoint.
func MakeHTTPHandler() http.Handler {
b := bone.New()
b.GetFunc("/version", mainflux.Version(protocol))
b.Handle("/metrics", promhttp.Handler())
return b
}
// MakeCOAPHandler creates handler for CoAP messages.
func MakeCOAPHandler(svc coap.Service, tc mainflux.ThingsServiceClient, l log.Logger, responses chan<- string) gocoap.Handler {
auth = tc
logger = l
r := mux.NewRouter()
r.Handle("/channels/{id}/messages", gocoap.FuncHandler(receive(svc))).Methods(gocoap.POST)
r.Handle("/channels/{id}/messages", gocoap.FuncHandler(observe(svc, responses)))
r.NotFoundHandler = gocoap.FuncHandler(notFoundHandler)
return r
}
func authorize(msg *gocoap.Message, res *gocoap.Message, cid uint64) (uint64, error) {
// Device Key is passed as Uri-Query parameter, which option ID is 15 (0xf).
key, err := authKey(msg.Option(gocoap.URIQuery))
if err != nil {
switch err {
case errBadOption:
res.Code = gocoap.BadOption
case errBadRequest:
res.Code = gocoap.BadRequest
}
return 0, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
id, err := auth.CanAccess(ctx, &mainflux.AccessReq{Token: key, ChanID: cid})
if err != nil {
e, ok := status.FromError(err)
if ok {
switch e.Code() {
case codes.PermissionDenied:
res.Code = gocoap.Forbidden
default:
res.Code = gocoap.ServiceUnavailable
}
return 0, err
}
res.Code = gocoap.InternalServerError
}
return id.GetValue(), nil
}
func receive(svc coap.Service) handler {
return func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message {
// By default message is NonConfirmable, so
// NonConfirmable response is sent back.
res := &gocoap.Message{
Type: gocoap.NonConfirmable,
// According to https://tools.ietf.org/html/rfc7252#page-47: If the POST
// succeeds but does not result in a new resource being created on the
// server, the response SHOULD have a 2.04 (Changed) Response Code.
Code: gocoap.Changed,
MessageID: msg.MessageID,
Token: msg.Token,
Payload: []byte{},
}
if msg.IsConfirmable() {
res.Type = gocoap.Acknowledgement
res.SetOption(gocoap.ContentFormat, gocoap.AppJSON)
if len(msg.Payload) == 0 {
res.Code = gocoap.BadRequest
return res
}
}
channelID := mux.Var(msg, "id")
cid, err := strconv.ParseUint(channelID, 10, 64)
if err != nil {
res.Code = gocoap.NotFound
return res
}
publisher, err := authorize(msg, res, cid)
if err != nil {
res.Code = gocoap.Forbidden
return res
}
rawMsg := mainflux.RawMessage{
Channel: cid,
Publisher: publisher,
Protocol: protocol,
Payload: msg.Payload,
}
if err := svc.Publish(rawMsg); err != nil {
res.Code = gocoap.InternalServerError
}
return res
}
}
func observe(svc coap.Service, responses chan<- string) handler {
return func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message {
res := &gocoap.Message{
Type: gocoap.Acknowledgement,
Code: gocoap.Content,
MessageID: msg.MessageID,
Token: msg.Token,
Payload: []byte{},
}
res.SetOption(gocoap.ContentFormat, gocoap.AppJSON)
id := mux.Var(msg, "id")
chanID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
res.Code = gocoap.NotFound
return res
}
publisher, err := authorize(msg, res, chanID)
if err != nil {
res.Code = gocoap.Forbidden
logger.Warn(fmt.Sprintf("Failed to authorize: %s", err))
return res
}
obsID := fmt.Sprintf("%x-%d-%d", msg.Token, publisher, chanID)
if msg.Type == gocoap.Acknowledgement {
responses <- obsID
return nil
}
if value, ok := msg.Option(gocoap.Observe).(uint32); (ok && value == 1) || msg.Type == gocoap.Reset {
svc.Unsubscribe(obsID)
}
if value, ok := msg.Option(gocoap.Observe).(uint32); ok && value == 0 {
res.AddOption(gocoap.Observe, 1)
o := coap.NewObserver()
if err := svc.Subscribe(chanID, obsID, o); err != nil {
logger.Warn(fmt.Sprintf("Failed to subscribe to NATS subject: %s", err))
res.Code = gocoap.InternalServerError
return res
}
go handleMessage(conn, addr, o, msg)
go ping(svc, obsID, conn, addr, o, msg)
go cancel(o)
}
return res
}
}
func cancel(observer *coap.Observer) {
<-observer.Cancel
close(observer.Messages)
observer.StoreExpired(true)
}
func handleMessage(conn *net.UDPConn, addr *net.UDPAddr, o *coap.Observer, msg *gocoap.Message) {
notifyMsg := *msg
notifyMsg.Type = gocoap.NonConfirmable
notifyMsg.Code = gocoap.Content
notifyMsg.RemoveOption(gocoap.URIQuery)
for {
msg, ok := <-o.Messages
if !ok {
return
}
payload, err := json.Marshal(msg)
if err != nil {
logger.Warn(fmt.Sprintf("Failed to parse received message: %s", err))
continue
}
notifyMsg.Payload = payload
notifyMsg.MessageID = o.LoadMessageID()
buff := new(bytes.Buffer)
observe := uint64(notifyMsg.MessageID)
if err := binary.Write(buff, binary.BigEndian, observe); err != nil {
logger.Warn(fmt.Sprintf("Failed to generate Observe option value: %s", err))
continue
}
observeVal := buff.Bytes()
notifyMsg.SetOption(gocoap.Observe, observeVal[len(observeVal)-3:])
if err := gocoap.Transmit(conn, addr, notifyMsg); err != nil {
logger.Warn(fmt.Sprintf("Failed to send message to observer: %s", err))
}
}
}
func ping(svc coap.Service, obsID string, conn *net.UDPConn, addr *net.UDPAddr, o *coap.Observer, msg *gocoap.Message) {
pingMsg := *msg
pingMsg.Payload = []byte{}
pingMsg.Type = gocoap.Confirmable
pingMsg.RemoveOption(gocoap.URIQuery)
// According to RFC (https://tools.ietf.org/html/rfc7641#page-18), CON message must be sent at least every
// 24 hours. Since 24 hours is too long for our purposes, we use 12.
t := time.NewTicker(12 * time.Hour)
defer t.Stop()
for {
select {
case _, ok := <-t.C:
if !ok || o.LoadExpired() {
return
}
o.StoreExpired(true)
timeout := float64(coap.AckTimeout)
logger.Info(fmt.Sprintf("Ping client %s.", obsID))
for i := 0; i < coap.MaxRetransmit; i++ {
pingMsg.MessageID = o.LoadMessageID()
gocoap.Transmit(conn, addr, pingMsg)
time.Sleep(time.Duration(timeout * coap.AckRandomFactor))
if !o.LoadExpired() {
break
}
timeout = 2 * timeout
}
if o.LoadExpired() {
svc.Unsubscribe(obsID)
return
}
case <-o.Cancel:
return
}
}
}
+24
View File
@@ -0,0 +1,24 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import "strings"
func authKey(opt interface{}) (string, error) {
val, ok := opt.(string)
if !ok {
return "", errBadRequest
}
arr := strings.Split(val, "=")
if len(arr) != 2 || strings.ToLower(arr[0]) != "authorization" {
return "", errBadOption
}
return arr[1], nil
}
+64
View File
@@ -0,0 +1,64 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package ats contains NATS message publisher implementation.
package nats
import (
"fmt"
"github.com/golang/protobuf/proto"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/coap"
broker "github.com/nats-io/go-nats"
)
const prefix = "channel"
var _ mainflux.MessagePublisher = (*natsPublisher)(nil)
type natsPublisher struct {
nc *broker.Conn
}
// New instantiates NATS message publisher.
func New(nc *broker.Conn) coap.Broker {
return &natsPublisher{nc}
}
func (pubsub *natsPublisher) Publish(msg mainflux.RawMessage) error {
data, err := proto.Marshal(&msg)
if err != nil {
return err
}
subject := fmt.Sprintf("%s.%d", prefix, msg.Channel)
return pubsub.nc.Publish(subject, data)
}
func (pubsub *natsPublisher) Subscribe(chanID uint64, obsID string, observer *coap.Observer) error {
sub, err := pubsub.nc.Subscribe(fmt.Sprintf("%s.%d", prefix, chanID), func(msg *broker.Msg) {
if msg == nil {
return
}
var rawMsg mainflux.RawMessage
if err := proto.Unmarshal(msg.Data, &rawMsg); err != nil {
return
}
observer.Messages <- rawMsg
})
if err != nil {
return err
}
go func() {
<-observer.Cancel
sub.Unsubscribe()
}()
return nil
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package coap
import (
"sync"
"github.com/mainflux/mainflux"
)
// Observer is used to handle CoAP subscription.
type Observer struct {
// Expired flag is used to mark that ticker sent a
// CON message, but response is not received yet.
// The flag changes its value once ACK message is
// received from the client. If Expired is true
// when ticker is triggered, Observer should be canceled
// and removed from the Service map.
expired bool
// Message ID for notification messages.
msgID uint16
expiredLock, msgIDLock sync.Mutex
// Messages is used to receive messages from NATS.
Messages chan mainflux.RawMessage
// Cancel channel is used to cancel observing resource.
// Cancel channel should not be used to send or receive any
// data, it's purpose is to be closed once Observer canceled.
Cancel chan bool
}
// NewObserver instantiates a new Observer.
func NewObserver() *Observer {
return &Observer{
Messages: make(chan mainflux.RawMessage),
Cancel: make(chan bool),
}
}
// LoadExpired reads Expired flag in thread-safe way.
func (o *Observer) LoadExpired() bool {
o.expiredLock.Lock()
defer o.expiredLock.Unlock()
return o.expired
}
// StoreExpired stores Expired flag in thread-safe way.
func (o *Observer) StoreExpired(val bool) {
o.expiredLock.Lock()
defer o.expiredLock.Unlock()
o.expired = val
}
// LoadMessageID reads MessageID and increments
// its value in thread-safe way.
func (o *Observer) LoadMessageID() uint16 {
o.msgIDLock.Lock()
defer o.msgIDLock.Unlock()
o.msgID++
return o.msgID
}
+4
View File
@@ -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
+19 -1
View File
@@ -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:
@@ -199,3 +199,21 @@ services:
- 8880:8880
networks:
- mainflux-base-net
coap-adapter:
image: mainflux/coap:latest
container_name: mainflux-coap
depends_on:
- things
- nats
restart: on-failure
expose:
- 5683
environment:
MF_COAP_ADAPTER_PORT: 5683
MF_NATS_URL: nats://nats:4222
MF_THINGS_URL: things:8183
ports:
- 5683:5683
networks:
- mainflux-base-net
+1 -9
View File
@@ -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
View File
@@ -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
+131
View File
@@ -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
View File
@@ -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
+6 -2
View File
@@ -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
+30
View File
@@ -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
}
+63
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+7 -5
View File
@@ -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
```
+3 -3
View File
@@ -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,
+1 -1
View File
@@ -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 (
+3 -3
View File
@@ -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,
+3 -2
View File
@@ -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
+1 -1
View File
@@ -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("")
+1 -1
View File
@@ -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
+3 -2
View File
@@ -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{}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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 -
###
+74
View File
@@ -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
```
+192
View File
@@ -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
}
+54
View File
@@ -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
}
+20
View File
@@ -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
View File
@@ -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)
}
+248
View File
@@ -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
}
+82
View File
@@ -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
}
+45
View File
@@ -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
View File
@@ -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
+34 -6
View File
@@ -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 {
+1 -3
View File
@@ -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
View File
@@ -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
}
+382 -81
View File
@@ -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
View File
@@ -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
}
+152 -43
View File
@@ -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))
}
}
+16 -9
View File
@@ -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 {
+22 -62
View File
@@ -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
+2 -2
View File
@@ -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 {
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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{}
+163 -35
View File
@@ -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 {
+3 -3
View File
@@ -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
}
+1 -1
View File
@@ -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{}
+75 -22
View File
@@ -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))
}
}
+160
View File
@@ -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)
}
}
+56
View File
@@ -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)
}
+120
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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)
}
+10
View File
@@ -0,0 +1,10 @@
language: go
go:
- 1.5
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- $HOME/gopath/bin/goveralls -service=travis-ci
+14
View File
@@ -0,0 +1,14 @@
# coap-mux [![Build Status](https://travis-ci.org/dereulenspiegel/coap-mux.svg)](https://travis-ci.org/dereulenspiegel/coap-mux) [![Coverage Status](https://coveralls.io/repos/dereulenspiegel/coap-mux/badge.svg?branch=master&service=github)](https://coveralls.io/github/dereulenspiegel/coap-mux?branch=master) [![GoDoc](https://godoc.org/github.com/olebedev/config?status.png)](https://godoc.org/github.com/dereulenspiegel/coap-mux)
This package provides basic support for routing based on path and method for
CoAP server. This library is heavily inspired by [mux](https://github.com/gorilla/mux).
## Installation
`go get github.com/dereulenspiegel/coap-mux`
## Usage
Create a new router with `NewRouter()` and add routes with `NewRoute()`,
`Handle(path, handler)` or `Path(string)`. The returned route object can be
configured further.
+49
View File
@@ -0,0 +1,49 @@
package mux
import (
"fmt"
"net"
"strings"
coap "github.com/dustin/go-coap"
)
// methodMatcher matches the request against HTTP methods.
type methodMatcher []coap.COAPCode
func (m methodMatcher) Match(msg *coap.Message, addr *net.UDPAddr) bool {
for _, v := range m {
if v == msg.Code {
return true
}
}
return false
}
type coaptypeMatcher []coap.COAPType
func (m coaptypeMatcher) Match(msg *coap.Message, addr *net.UDPAddr) bool {
for _, v := range m {
if v == msg.Type {
return true
}
}
return false
}
// addRegexpMatcher adds a host or path matcher and builder to a route.
func (r *Route) addRegexpMatcher(tpl string) error {
if len(tpl) == 0 || tpl[0] != '/' {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
}
if r.regexp != nil {
tpl = strings.TrimRight(r.regexp.template, "/") + tpl
}
rr, err := newRouteRegexp(tpl)
if err != nil {
return err
}
r.regexp = rr
r.addMatcher(rr)
return nil
}

Some files were not shown because too many files have changed in this diff Show More