Compare commits

..

202 Commits

Author SHA1 Message Date
Nikola Marčetić 6af65ea159 Added changelog for 0.9.0 release (#799)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-19 17:56:23 +02:00
Aleksandar Novaković b95bb82e0b Fix users service to work in offline mode (#795)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-19 12:28:45 +02:00
Ivan Milošević 8bda799ef9 Create and push docker manifest for new release from Makefile (#794)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-18 17:31:37 +02:00
Aleksandar Novaković 61f1c2e379 MF-399 - Add open tracing support (#782)
* Add open tracing dependencies

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to users service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the http adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the ws adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the CoAP adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update LoRa adapter in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update SDK tests in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update reader services with accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update .env and docker-compose file

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add jaeger and timeout env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix broken test for can access by id endpoint

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update deps with proto empty package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-18 15:01:09 +02:00
Ivan Milošević 1ad14e7fd3 fix mainflux_id parameter in bootstrap swagger (#789)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-16 16:21:37 +02:00
Aleksandar Novaković a6a8648e4f MF-783 - Allow access checking by a thing ID (#784)
* Add can access by things ID endpoint to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new auth endpoint to the swagger docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add test for the new endpoint of the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-15 18:28:15 +02:00
Dušan Borovčanin 4a7af7c0b3 NOISSUE - Fix CoAP adapter (#779)
* Fix CoAP mux

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

* Remove prints and commented code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-15 17:49:02 +02:00
Ivan Milošević b0a79436b8 Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-11 23:19:08 +02:00
Aleksandar Novaković 4ab2e396c2 NOISSUE - Add authorization HTTP API to things service (#772)
* Add authorization HTTP API to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new tests and update existing ones

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update swagger documentation

Update swagger documentation for auth endpoints.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update README docs for things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docker-compose and fix endpoint typo

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove commented code

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 17:06:55 +02:00
Manuel Imperiale 22a3b881fd NOISSUE - Typo fix (#777)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-07-04 09:57:03 +02:00
Aleksandar Novaković aeebf06799 Remove cli executable from repo (#776)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 05:19:49 +08:00
Mirko Teodorovic c396e6ded8 fix docker-compose env (#775)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-03 20:50:28 +02:00
Manuel Imperiale f5b6c63cca NOISSUE - Use .env vars in docker-compose (#770)
* NOISSUE - Use .enr vars in docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README and dupllicated .env in docker repo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Bootstrap Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Cassandra (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for InfluxDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for MongoDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Postgres (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for LoRa adapter Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Grafana port in .env file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove docker/.env and update README.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-02 14:22:12 +02:00
Dušan Borovčanin 81cd73e277 Use errors and null packets in authorized pub/sub (#773)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-01 11:22:31 +02:00
Mirko Teodorovic 204aec6171 MF-663 - enable nginx port conf from docker env (#769)
* MF-722 - Change UUID lib (#746)

* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-732 - Add Postgres reader (#740)

* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-742 - Things to support single user scenario (#749)

* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Update grpc and protobuf deps in mqtt adapter (#751)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Fix MQTT raw message deserialization (#753)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Add certificate fields to the Bootstrap service (#752)

* Add cert fields to the BS

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

* Add cert fields when creating a config

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

* Add update cert endpoint

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

* Fix key column name

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

* Add cert fields to db converters

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

* Secure cert update endpoint

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

* Authroize cert update methods

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

* Fix Bootstrap service tests

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

* Add cert update service tests

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

* Update endpoit tests

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

* Update API docs

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

* Update request tests

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

* Fix request tests

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

* Update repository tests

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

* Fix typo in repo tests

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

* NOISSUE - Add searchable Things name (#750)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Add missing Websocket.js into docker ui image (#755)

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

* MF-466 - ARM docker deployment (#756)

* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

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

* docker compose file

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

* remove bin qemu file

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

* remove unnecesarry comments

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

* Add make dockers_arm32v7 in Makefile

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

* Remove docker/Dockerfile.arm

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

* Add build for arm32v7 in ui Makefile

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

* Add arm32v7 tags

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

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

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

* fix thing database env variable

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

* Switch back to separate database for things and users

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

* rename arm32v7 to arm

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

* remove arm32v7 tag
rename arm32v7 image names

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

* Rename command for making mqtt and ui individual images, to match other miscroservices

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

* Push arm docker images

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

* fix fucntion call in ci.sh

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

* mergiing ui/Dockerfile

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

*  NOISSUE - Add searchable Channels name  (#754)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Remove installing non-existent package in ci (#758)

* Remove installing non-existent  package from ci

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

* remove branch master condition in ci.sh to test it

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

* fix syntax error

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

* update apt

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

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

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

* clean docker after pushing amd64 images

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

* installing all qemu app

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

* remove docker images before building ui and mqtt arm-images

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

* prune dockers and test only arm build

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

* fix syntax error

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

* fix moving qemu-arm-static

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

* installing qemu with apt-get

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

* add another apt-get update

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

* apt install one liner

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

* testing ci script

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

* After installing qemu, get appropriate version

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

* Test ci without amd64 dockers

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

* remove tests from ci for test purpose

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

* Uncomment commands

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

* Remove whitespace and change order of building images

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

* nginx

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* change to openresty

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* change to openresty and accept env from docker compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Fix Docker for ARM (#760)

* NOISSUE - Fix Docker for ARM

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

* Correct ARGs

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

* Fix docker-compose for ARM

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

* Add docker manifest

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

* cp not mv qemu from mf root

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

* fix amd64 docker names for ui and mqtt images

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

* edit ci.sh for testing purposes

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

* remove bachslash from make manifest
edits in ci for testing purposes

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

* fix manifest call

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

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

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

* include tests

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

* docker system prune
remove tests for testin purposes

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

* Add variant in manifest file for armv7

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

* Remove white space

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

* paralelise the compilation

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

* fix place of -j$NPROC

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

* adding env variable

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* fix variant option for manifest annotate (#765)

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

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* use docker env to set port

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* add env to conf port in nginx.conf

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* replace string with docker env

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* replace string with docker env

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Update docs (#766)

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

* remove not needed comment

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* adding .env file for default UI_PORT - if no enviroment UI_PORT is set value from .env file is used

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* get default UI_PORT variable value from .env file

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* set default port value to 3000

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-06-24 20:13:02 +02:00
Dušan Borovčanin aa219b6f99 NOISSUE - Fix count when search by name is performed (#767)
* Fix total field when search by name

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

* Fix tests to check total count

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-24 14:03:48 +02:00
Dušan Borovčanin f27cb1d019 Update docs (#766)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-21 14:07:24 +02:00
Ivan Milošević 41897cee11 fix variant option for manifest annotate (#765)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 15:31:56 +02:00
Drasko DRASKOVIC 91274aca9b NOISSUE - Fix Docker for ARM (#760)
* NOISSUE - Fix Docker for ARM

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

* Correct ARGs

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

* Fix docker-compose for ARM

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

* Add docker manifest

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

* cp not mv qemu from mf root

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

* fix amd64 docker names for ui and mqtt images

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

* edit ci.sh for testing purposes

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

* remove bachslash from make manifest
edits in ci for testing purposes

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

* fix manifest call

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

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

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

* include tests

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

* docker system prune
remove tests for testin purposes

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

* Add variant in manifest file for armv7

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

* Remove white space

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

* paralelise the compilation

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

* fix place of -j$NPROC

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 14:14:06 +02:00
Ivan Milošević b53979104c NOISSUE - Remove installing non-existent package in ci (#758)
* Remove installing non-existent  package from ci

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

* remove branch master condition in ci.sh to test it

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

* fix syntax error

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

* update apt

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

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

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

* clean docker after pushing amd64 images

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

* installing all qemu app

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

* remove docker images before building ui and mqtt arm-images

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

* prune dockers and test only arm build

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

* fix syntax error

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

* fix moving qemu-arm-static

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

* installing qemu with apt-get

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

* add another apt-get update

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

* apt install one liner

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

* testing ci script

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

* After installing qemu, get appropriate version

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

* Test ci without amd64 dockers

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

* remove tests from ci for test purpose

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

* Uncomment commands

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

* Remove whitespace and change order of building images

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-12 21:11:45 +02:00
Manuel Imperiale 63de955a7c NOISSUE - Add searchable Channels name (#754)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-06-11 10:37:25 +02:00
Ivan Milošević 44cc20b9ca MF-466 - ARM docker deployment (#756)
* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

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

* docker compose file

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

* remove bin qemu file

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

* remove unnecesarry comments

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

* Add make dockers_arm32v7 in Makefile

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

* Remove docker/Dockerfile.arm

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

* Add build for arm32v7 in ui Makefile

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

* Add arm32v7 tags

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

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

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

* fix thing database env variable

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

* Switch back to separate database for things and users

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

* rename arm32v7 to arm

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

* remove arm32v7 tag
rename arm32v7 image names

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

* Rename command for making mqtt and ui individual images, to match other miscroservices

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

* Push arm docker images

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

* fix fucntion call in ci.sh

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

* mergiing ui/Dockerfile

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-11 02:14:05 +02:00
Ivan Milošević 62e7d69f7b Add missing Websocket.js into docker ui image (#755)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-10 00:15:12 +02:00
Manuel Imperiale c8979ac297 NOISSUE - Add searchable Things name (#750)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-30 15:33:49 +02:00
Dušan Borovčanin cac1e35b58 NOISSUE - Add certificate fields to the Bootstrap service (#752)
* Add cert fields to the BS

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

* Add cert fields when creating a config

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

* Add update cert endpoint

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

* Fix key column name

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

* Add cert fields to db converters

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

* Secure cert update endpoint

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

* Authroize cert update methods

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

* Fix Bootstrap service tests

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

* Add cert update service tests

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

* Update endpoit tests

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

* Update API docs

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

* Update request tests

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

* Fix request tests

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

* Update repository tests

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

* Fix typo in repo tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-22 23:22:19 +02:00
Aleksandar Novaković 6d6f3cc858 Fix MQTT raw message deserialization (#753)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-21 11:27:26 +02:00
Aleksandar Novaković 066057d914 Update grpc and protobuf deps in mqtt adapter (#751)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:51:22 +02:00
Aleksandar Novaković f9093413ee MF-742 - Things to support single user scenario (#749)
* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:06:21 +02:00
Manuel Imperiale 5fec0a9e92 MF-732 - Add Postgres reader (#740)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-16 18:00:19 +02:00
Aleksandar Novaković 3125f0bbc2 MF-722 - Change UUID lib (#746)
* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:35:13 +02:00
Aleksandar Novaković 0dc4d12e73 Add performance improvement to writer filtering (#744)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:19:05 +02:00
Dušan Borovčanin 9d30006d31 NOISSUE - Update nginx version (#748)
* Update version of the Nginx image

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

* Add undefined check for HTTP and WS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-16 11:59:23 +02:00
Aleksandar Novaković 5202d734c9 MF-574 - Add missing environment variables to Cassandra writer (#745)
* Add username, password and port env var

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing readme files with new env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 23:31:40 +02:00
Aleksandar Novaković cc5c66bfba Fix CI with fixed plugin versions (#747)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 17:41:18 +02:00
Dušan Borovčanin f5bb65d0e4 NOISSUE - Add compile test to CI (#743)
* Add compile tests to CI script

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

* Fix parallelization flag

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-13 14:48:54 +02:00
Mirko Teodorovic e46164f93f fix building problems (#741)
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-05-11 17:16:25 +02:00
Manuel Imperiale d2fc379e3f NOISSUE - Fix Postgres logs in Things service (#734)
* NOISSUE - Fix Postgres logs in Things service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix uuid package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-10 18:55:13 +02:00
Aleksandar Novaković 5e5fe88927 MF-708 - Assign Writer(s) to a channel (#737)
* Add support for channel filtering using config

Add support for channel filtering using yaml configuration files
for writers.

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

* Update writers documentation with new env var

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

* Add info comment to configuration file

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

* Fix configuration and update yaml dep

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

* Update config from yaml to toml

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-05-10 14:36:27 +02:00
Manuel Imperiale 886c8b2467 MF-732 - Add PostgreSQL writer (#733)
* MF-732 - Add PostgreSQL writer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Unify writers api

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix host and port

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 19:09:28 +02:00
Manuel Imperiale 138dae9277 NOISSUE - Fix Readers logs (#735)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:59:18 +02:00
Manuel Imperiale 9f1151b8dc NOISSUE - Add readers pagination in SDK (#736)
* NOISSUE - Add readers pagination in SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:10:02 +02:00
Darko Draskovic 700cfdf6f3 Add UI websocket open/close and send/receive (#728)
* Add websocket open/close and send/receive

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket feedback/log to Elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Align WS open/close buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket query for channel list

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket related encoders/decoders for port interops

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix .js indentation and simplify ws (dis)connect method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use single quotes consistently

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add HTTP Send and WS Listen

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-26 19:17:04 +02:00
Aleksandar Novaković dc9333237f MF-707 - Allow custom Thing key (#726)
* Add support for setting up thing key manually

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

* Fix existing tests and add new ones

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

* Update SQL schema for things entity

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

* Add update thing key endpoint to swagger docs

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

* Fix response code when handling conflicting key

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 14:37:51 +02:00
Aleksandar Novaković aba7d909ed MF-525 - Add pagination response to the readers (#729)
* Add pagination response to the readers

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

* Remove println from influx reader

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 00:18:43 +02:00
Manuel Imperiale 04281f2388 NOISSUE - Rm Things type from lora-adapter (#727)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-22 21:31:08 +02:00
Milan Andric 08f2545df9 fix to makefile for OSX/Darwin (#724)
Signed-off-by: Milan Andric <mandric@gmail.com>
2019-04-22 21:03:05 +02:00
Darko Draskovic 64f154dcc9 Fix .dockerignore file by removing index.html (#725)
* Fix .dockerignore file by removing index.html

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add autogen main.js to .dockerignore

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 20:10:24 +02:00
Darko Draskovic 9688fc316a Fix things and channels metadata create and edit & remove thing type (#721)
* Add basic websocket support

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add separate cards for base and regular attribs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary json metadata enc/dec

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add JsonMF module for JSON val handling

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename json related func for naming consistency

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove Thing type from user interface

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary metadata json to channels

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change json string identation

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 02:40:14 +02:00
Jovan Kostovski 5b66b6e169 skip deleting of persistent volumes by default (#723)
This commit, intruduces changes to the current behavior of the "make cleandocker" command.
From now on, an extra parameter must be given in order to delete the persistent volumes,  which up until now was done by default. The new command which represents the old behavior (deleting all artifacts created from the docker composition) is "make pv=true cleandocker".

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-20 17:56:51 +02:00
Manuel Imperiale b97deb50b2 MF-488 - Remove Thing type (app or device) (#718)
* MF-488 - Remove Thing type (app or device)

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-20 14:09:11 +02:00
Dušan Borovčanin 44615c5ff0 Remove empty channels check (#720)
Remove empty list of channels check from the default Bootstrap
reader implementation.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-18 18:02:20 +02:00
Jovan Kostovski a30666d999 MF-655 Proper usage of docker volumes (#657)
* added filter and a switch for volume deletion

Signed-off-by: Jovan Kostovski <chombium@gmail.com>

* added persistent volume configuration

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-18 17:31:24 +02:00
Drasko DRASKOVIC baebe8abc1 NOISSUE - Improve UI styling (#719)
* Add styling

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

* Fix errors

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

* Fix env

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-18 13:29:38 +02:00
Dušan Borovčanin d9d9da49a8 MF-715 - Conflict on updating connection with a valid list of channels (#716)
* Add check if Channels already exist

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

* Update streams tests to use time offset

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-17 19:41:37 +02:00
Aleksandar Novaković 2f17a05b3e MF-711 - Create separate Redis instance for ES (#717)
* Fix Redis connection error handling in MQTT adapter

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

* Add separate env vars for ES in MQTT adapter

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

* Create dedicated redis service for ES

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

* Update docs according to changes

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-17 12:39:47 +02:00
Dušan Borovčanin fe9f4dca8e NOISSUE - Update event fields naming (#713)
* Update event fields naming

Replace `id` with `thing_id`.
Fix typo in `success` field.

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

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-16 18:32:05 +02:00
Aleksandar Novaković de736a5c47 MF-698 - Add missing info and docs about sys event sourcing (#712)
* Add event sourcing section in dev guide

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

* Fix spelling in the docs

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

* Fix notice formatting in docs

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

* Fix typos in docs

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

* Add highlight for event type

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 17:28:43 +02:00
Aleksandar Novaković 5799356b14 MF-549 - Change metadata format from JSON string to JSON object (#706)
* Update metadata type in things service

Update things service so that metadata has map type. Update repo
implementation by adding sqlx lib.

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

* Add sqlx lib to bootstrap service

Add sqlx lib to bootstrap service and update metadata field type.

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

* Update metadata in redis streams consumer

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

* Update tests for bootstrap service

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

* Fix mongo reader logging and driver version

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

* Fix mongo reader and writer

Fix mongo reader and writer by updating driver version.

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

* Update SDK with new metadata format

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

* Update LoRa adapter with new metadata format

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

* Update users service in order to use sqlx

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

* Replace anonymous struct with map

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

* Update docs for LoRa adapter

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

* Fix LoRa application metadata format

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

* Fix metadata format in LoRa docs

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

* Add metadata2 var to SDK things test

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 14:58:56 +02:00
Dušan Borovčanin 0bbc4f54ab Fix Bootstrap service event map keys (#705)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-11 13:55:21 +02:00
Darko Draskovic 0b89975947 NOISSUE - Replace repeating code by card gen func (#697)
* Replace repeating code by card gen func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove absolute URL from Env.elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix md links in README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor appendIF helper func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-09 16:21:26 +02:00
Dušan Borovčanin df11a20a32 Update Bootstrap service docker-compose.yml (#700)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-09 02:44:50 -07:00
Ivan Milošević 77ce0f9fab Remove Debug function (#699)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-09 01:50:23 -07:00
Dušan Borovčanin 6ebc758f89 MF-687 - Add event sourcing to Bootstrap service (#695)
* Add initial Bootstrap event producer

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

* Add operation name to events

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

* Add ES path for BS events

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

* Add BS event sourcing tests

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

* Add connections update events

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

* Extract event XADD to separeate method

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

* Add header comments

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

* Fix service docs typo

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

* Update BS service README.md

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

* Update Service method tests

Tests for handler methods are not added.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-08 08:46:07 -07:00
Ivan Milošević 98d588e8ef NOISSUE - Remove debugging message from response of handle error function (#696)
* Remove debugging message from response of handle error function

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

* Print error in debug console in case of bad body HTTP response

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-08 13:44:11 +02:00
Aleksandar Novaković 12a85f565c Fix logging in publish event callback (#694)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 17:29:43 +02:00
Aleksandar Novaković 5fcf0aa229 Add event stream to MQTT adapter for conn status (#692)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 11:25:39 +02:00
Drasko DRASKOVIC 9b41c2db3a NOISSUE - Improve UI style (#691)
* Improve UI style

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

* Remove unused font

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-04 20:28:56 +02:00
Dušan Borovčanin e4d3b1d8c3 Fix InfluxDB time bug (#689)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 18:10:01 +02:00
Dušan Borovčanin 4f62f89b87 Update docs structure (#686)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 12:10:46 +02:00
Dušan Borovčanin 06f4c6e059 Use images instead of carousel (#685)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 11:19:43 +02:00
Dušan Borovčanin 25d9783514 NOISSUE - Update docs (#683)
* Add authorization section to the docs

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

* Slow down Bootstrap gif

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

* Use heading 4 instead of italic

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

* Replace gif with carousel

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 10:22:09 +02:00
Ivan Milošević 56a50962b6 MF-662 - Change menu style (#678)
* env variable for local dev

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

* Change menu style

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

* Revert "env variable for local dev"

This reverts commit 205e13be6cdde930f02e7bda2019bb615267ed14.

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

* Move font awesome class names to record in Helper

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-02 19:17:28 +02:00
Dušan Borovčanin f9b17d5f24 MF-651 - X509 Mutual TLS authentication (#676)
* Use NginX njs module for mutual authentication

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

* Add Makefile for cert management

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

* Move certificates make context to scripts dir

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

* Move nginx.conf to separate directory

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

* Choose between two NginX configurations

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

* Move certs Makefile to docker/ssl/

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

* Use default key-based authentication

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

* Add mTLS docs

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

* Update Makefile

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

* Add check if Authorization is present

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

* Add check if Will Flag is 1

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

* Return MQTT over WS

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

* Fix docker-compose.yml volume mapping

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

* Rename security section in docs

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

* Add message type check before message parsing

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

* Remove double comments

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

* Remove s.AGAIN in return

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

* Update Makefile

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

* Remove CSR and key from the root

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

* Drop TLS version below 1.2

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

* Add comments for cert and key paths

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-02 17:54:24 +02:00
Ivan Milošević bc49f6a543 Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-01 17:49:10 +02:00
Aleksandar Novaković 83cafeb6be Update Aedes version for MQTT adapter (#677)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-29 14:04:02 +01:00
Ivan Milošević c634bf8c7d MF-661 - Bootstrap pagination in UI (#672)
* Add bootstrap pagination

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

* Fix enviroment variable in readme

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

* Change style
Remove pagination from channels and things lists if total less then limit

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

* Hide pagination when not needed on connection and message page
Code refacotoring

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

* Remove debug module and local dev variable.

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

* Remove debug logging

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

* Add trailing slash note in README

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

* proto

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-03-28 10:20:15 +01:00
Aleksandar Novaković 86a3115804 Update subtopics section in documentation (#670)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-22 16:47:30 +01:00
Darko Draskovic 4a31f11615 Remove default base URL value (#671)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-22 14:53:03 +01:00
Nikola Marčetić ea79f22269 Changelog updates regarding to 0.8 Milestone (#669)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-03-20 18:10:32 +01:00
Nikola Marčetić 767c900c48 Bump version to 0.8 (#668)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-03-20 17:44:38 +01:00
Darko Draskovic d504c4f67d MF-571 - Add Env.elm to set custom base URL (#654)
* Add Env module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Modify README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove literal vals from Env.elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace protocol, host and port env setting by url

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove default env.url value and update README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add http://localhost:80/ as a default baseURL val

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace Gateflux by Mainflux

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-20 14:30:38 +01:00
Jovan Kostovski e95e010a3a added docs about docker-compose config overriding (#653)
Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-03-19 22:32:07 +01:00
Aleksandar Novaković a09e3e214a NOISSUE - Fix subtopic regex and restrict empty subtopic parts (#659)
* Fix subtopic regex and restrict empty subtopic parts

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

* Update mqtt adapter subtopic processing

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

* Return old MQTT adapter implementation

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

* Allow dots and handle empty parts

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

* Add malformed subtopic error encoding

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

* Fix MQTT topic validation

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

* Fix MQTT topic parsing

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

* Fix subtopic filtering

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-19 16:05:56 +01:00
tony-ward 7c4c6109f4 MF-639 Split Content-Type header field on semicolon and evaluate all substrings (#644)
* Split Content-Type header field on semicolon and evaluate all substrings

Signed-off-by: Tony Ward <tony@lucidtron.com>

* included strings library
Signed-off-by: Tony Ward <tony0ward@hotmail.com>

* simplify code, run gofmt

Signed-off-by: tony <tony0ward@hotmail.com>
Signed-off-by: tony <tony@lucidtron.com>

* allow Content-Type to only contain the required type

Signed-off-by: Tony Ward <tony@lucidtron.com>
2019-03-19 12:00:11 +01:00
Aleksandar Novaković 8c3bff5f4b MF-656 - Change bootstrap service port to 8200 (#658)
* Change bootstrap service port to 8200

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

* Fix redis streams tests in things service

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-18 13:50:44 +01:00
Dušan Borovčanin 2ed1471d5f MF-539 - Improve Bootstrap Service documentation (#646)
* Fix infinte loop in Subscribe method

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

* Update API docs

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

* Update service README

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

* Update docs

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

* Add bootstrapping flow gif

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

* Update bootstrapping flow gif

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-03-15 19:11:26 +01:00
beres 61b2d6b87b MF-596 - Add subtopic to RawMessage (#642)
* Commit for mainflux-596
Modified and tested:
- cli
- http
- mqtt
- normalizer
- all readers
- sdk messages
- all writers
- ws
Missing:
- coap
- lora

Signed-off-by: ale <ale@metaverso.org>

* - fix subtopic name in, when starting with dot, http/ws/mqtt
- add some test on readers

Signed-off-by: ale <ale@metaverso.org>

* - fix string concatenation
- update http/transport regexp to match subtopic names with only \w-
- update ws/transport regexp like http ones with also the wildcard * and >

Signed-off-by: ale <ale@metaverso.org>

* added subtopic support to coap adapter

Signed-off-by: ale <ale@metaverso.org>

* - update replace functions with replaceall when needed
- renamed getDestChannel to fmtSubject
- update api/transport and ws/transport route to be more readable
- fix mqtt syntax
- renamed func andQuery to query as suggested by @anovakovic01
- have a nice we :)

Signed-off-by: ale <ale@metaverso.org>

* - fix error declaration on ws/nat/publisher
- fix regexp added missing allowed chars - and _ on coap/api/transport
- fix subtopic clean suffix / if present on coap/api/transport
- improve regexp on http and ws /api/transport, now does not accept url that do not strictly match
- add some ws subtopic tests

Signed-off-by: ale <ale@metaverso.org>

* - enabled wildcard chars on coap/api/transport
- allow use special chars on http and ws api/transport

Signed-off-by: ale <ale@metaverso.org>

* - use strings.Replace() insted ReplaceAll()

Signed-off-by: ale <ale@metaverso.org>

* - allow every chars on subtopics
- fix replace error on mqtt

Signed-off-by: ale <ale@metaverso.org>

* fix cassandra test

Signed-off-by: ale <ale@metaverso.org>

* fix ws test with invalid subtopic

Signed-off-by: ale <ale@metaverso.org>

* fix invalid GOCACHE in go1.12, replaced by -count 1, see https://golang.org/doc/go1.10#test

Signed-off-by: ale <ale@metaverso.org>

* - improve regexp on http/ws api/transport
- minor changes

Signed-off-by: ale <ale@metaverso.org>

* - add generic function parseSubtopic on ws/http adapters

Signed-off-by: ale <ale@metaverso.org>

* - add generic function fmtSubtopic on coap adapter

Signed-off-by: ale <ale@metaverso.org>
2019-03-15 18:38:07 +01:00
Manuel Imperiale 2f09701028 NOISSUE - Prevent infinite loop in lora-adapter if Redis init fail (#647)
* NOISSUE - Prevent infinite loop in lora-adapter if Redis init fail

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix line length

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-03-14 18:10:51 +01:00
Darko Draskovic a0ed73d09b Replace crossOrigin with relative path and fix messaging bug (#645)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-13 13:58:03 +01:00
Alwin Doss 74dfd69767 Corrected grammar and rephrased a few sentences to read nicely (#641)
Signed-off-by: Alwin Doss <alwindoss84@gmail.com>

Incorporated review comments

Signed-off-by: Alwin Doss <alwindoss84@gmail.com>

Fixed grammar

Signed-off-by: Alwin Doss <alwindoss84@gmail.com>
2019-03-07 16:25:37 +01:00
Darko Draskovic b2ce9f5597 Fix missing css in container ui (#638)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-07 13:39:35 +01:00
Darko Draskovic 3255f32a9c MF-571 - Elm UI (#632)
* Initial commit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add user create form

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Makefile and README

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove elm-stuff

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add url parsing

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add channels retrive and remove

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor request f and add f annotations

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add things provision

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add thing retrieve and remove

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename Channel.elm methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix trailing slash and add url constants

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add module User with separate model, view and update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Modularize channels

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Modularize version and things

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor module methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Modularize messaging

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add channel pagination

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix initial channel model

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add access control (connect and disconnect things from channel)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add pagination to Things

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Using Url.Builder in connection and messaging
Rename Acces module to Connection
Rename attribute in Bootstrap code
Remove debug logging

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add login

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add dynamic menu

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix empty name channel/thing bug

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove menu from login screen and add response helper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add version, channels, things direct display

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add channels table

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change var names and add refreshChannelList helper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Things list

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix response behavior

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove version page button

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

* Move everything to ui folder

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

* Remove dashflux

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

* MF-571 Add things and channels lists to Connection module (#580)

* Add thing and channel lists to Connection module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix form reset bug & remember checkbox status in Connection module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - Add batch things-channels connection modification (#588)

* Add batch things-channels connection modification

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove unused token arg and reposition buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* NOISSUE - messaging in UI (#578)

* Add thing and channel lists to Connection module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* init commit

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

* list channels and things

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

* list channels with radio buttons

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

* Listing channels for selected thing

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

* fix list channels for selected thing

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

* Use records instead of hardcode values
Use genFormField from Helpers
Remove dead comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* NOISSUE - Add Docker build for UI (#582)

* Add Docker build for UI

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

* Fix entrypoint and ports

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

* Add NginX server. Fix port.

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* NOISSUE - Fix layout and add custom CSS (#593)

* Fix layout and add custom CSS

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

* Remove comments, add copyright headers

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

* Add newline at the end of the css file

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* NOISSUE -update message module (#599)

* Update message module
Sending message as selected thing to multiple channels

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

* Rename token to thingkey

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

* Reset model on send message and reset list of channels on selecting thing

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove logout button and add logout header (#604)

* Remove logout button and add logout header

* Removed dead code.

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - Pagination (#606)

* Add total # things to Thing model

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add basic pagination for Thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add pagination helper function & Channel pagination

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* NOISSUE - Add bootstrap cards in version view (#607)

* Add total # things to Thing model

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add basic pagination for Thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add bootstrap cards in version view

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

* Add pagination helper function & Channel pagination

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* modify styling

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

* Styling

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

* Mainflux logo

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Revert "NOISSUE - Add bootstrap cards in version view (#607)"

This reverts commit a9a4dd7c73.

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Pagination (#613)

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - Add dashboard (#614)

* Add Dashboard module and simplify Main module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove unused code and Version module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add helper funcs to Main.elm update method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Improve Main and Message module organization

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add dashboard Things and Channels buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Modal to edit thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add RemoveThing msg and AwesomeFont

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Unify provision and retrieve request

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Reorganize code by making funcs and code repositioning

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add fon-awesome 5.* support

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add metadata edit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove table header add thing and removed dead code

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - UI modal buttons (#617)

* Add Dashboard module and simplify Main module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove unused code and Version module

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add helper funcs to Main.elm update method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Improve Main and Message module organization

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add dashboard Things and Channels buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Modal to edit thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add RemoveThing msg and AwesomeFont

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Unify provision and retrieve request

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Reorganize code by making funcs and code repositioning

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add fon-awesome 5.* support

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add metadata edit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove table header add thing and removed dead code

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add complete Thing edit modal buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add card style to channels (#618)

Add dropdown user menu
Add font awesome to main menu

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - UI Thing module with modals (#620)

* Add Thing provision modal

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add app/dev dropdown to Thing provision modal

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor Thing to have separate Update and Provision msg handling

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove fontAwesome import and fix Dict import

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - UI channel modals (#621)

* Add Thing provision modal

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add app/dev dropdown to Thing provision modal

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor Thing to have separate Update and Provision msg handling

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove fontAwesome import and fix Dict import

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add provision and edit modals to Channel

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add HttpMF http custom library

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add ModalMF Bootstrap modal custom library

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Moved all http funcs to HttpMF

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Moved expect funcs from Connection and Message to HttpMF

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add cards and change tables (#622)

* Change type of tables
Add tables to cards
Colorize buttons

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

* remove comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* MF-571 - Fix user login and refactor User (#623)

* Fix user login and refactor User

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove redundant Grid.row nesting

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix links in README (#624)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix ui/Makefile and ui/README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add copyright header to .elm files

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix missing README.md after migration

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix typo

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add install options to README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename Url to URL and Id to ID

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-06 16:21:09 +01:00
Vladimir Starčević dbd8f9be23 MF-579 Things & Channels returns 404 when not found or ID is malformed, not 500 (#633)
* Things returns 404 when not found, not 500

Signed-off-by: Vladimir Starcevic <vstarcevic@gmail.com>

* MF-579 Things returns 404 when not found or thingid is in invalid format

Signed-off-by: Vladimir Starcevic <vstarcevic@gmail.com>

* MF-579 Things and channels returns 404 when not found, or ID is malformed not 500

Signed-off-by: Vladimir Starcevic <vstarcevic@gmail.com>

* MF-579 Things & Channels returns 404 when not found or ID is malformed, not 500

Signed-off-by: Vladimir Starcevic <vstarcevic@gmail.com>
2019-03-06 14:10:01 +01:00
Dušan Borovčanin 3300814026 MF-552 - Use event sourcing to keep Bootstrap service in sync with Things service (#603)
* Use separate table for Channels

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

* Add inital event sourcing subscription

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

* Add Channel update sync

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

* Add Thing remove sync

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

* Add Channel remove sync

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

* Update service add method marshalling metadata

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

* Make separate methods for connection update

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

* Add diconnect event sync

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

* Update Configs repository mock

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

* Fix service tests

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

* Update repository tests

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

* Update API docs

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

* Update Location header

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

* Update README.md

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

* Fix tests mutex lock

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

* Fix method names in logs

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-03-04 17:41:38 +01:00
Manuel Imperiale a60a5c1ba1 NOISSUE - Fix lora-adapter Object decode (#610)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-02-22 15:30:37 +01:00
Dušan Borovčanin fa7d638453 MF-540 - Add pagination in API responses for Bootstrap service (#575)
* Add Page to List response

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

* Add request validation tests

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

* Update endpoint routes

Update API docs accordingly.

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

* Add optional Thing ID to config add request

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

* Extract literals to constants

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

* Update comments

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

* Fix count logs

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-02-22 14:54:09 +01:00
Manuel Imperiale 1c09632671 MF-600 - Handle custom LoRa Server application decoder (#608)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-02-22 09:31:44 +01:00
Ivan Milošević 06921fbee6 Fix run command in dev guide (#605)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-02-21 15:56:51 +01:00
caztial b69d31817d update docker-compose (#590)
add udp instead of default tcp to the docker coap port.
2019-02-20 00:17:13 +01:00
Dušan Borovčanin 225f56fc3e Update generated code (#602)
Use latest release of `protobuf` 3.6.1.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-02-18 17:10:25 +01:00
Dušan Borovčanin 133383ecb7 Add generated files check (#601)
Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-02-17 18:40:30 +01:00
Mirko Teodorovic 2ee0ed18e0 MF-597 - Removed legacy code as not needed anymore (#598)
* Removed legacy code as not needed anymore

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Small correction

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Remove git as not needed

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-02-15 18:35:52 +01:00
mteodor c418a9a4cf NOISSUE - Added normalizer service to run script (#594)
* Added normalizer service to be run

* Update run.sh
2019-02-13 12:05:29 +01:00
Kai-Chu Chung 351d6e7bfb MF-583 - Correct cmd/mongodb-reader HTTPServer log Info (#584)
Signed-off-by: Kai-Chu Chung <cage.chung@gmail.com>
2019-02-12 11:36:58 +01:00
Nikola Marčetić 434a57d8e1 Changed RawMessage (#587)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-02-11 14:27:56 +01:00
Manuel Imperiale 94574e3707 NOISSUE - fix CLI log (#581)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-02-08 15:38:07 +01:00
Juanma Fernandez b12bb3374b NOISSUE - Fix users logs in main.go (#577)
Signed-off-by: juanmagal <juanma.galmes@gmail.com>
2019-02-07 08:43:43 +01:00
Dušan Borovčanin 3f3b42b511 MF-519 - Refine Message (#567)
* Enable Message JSON mashaling/unmarshaling

Update Protobuf generated files.

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

* Update readers and SDK to use Mainflux Message

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

* Fix alignment

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-02-06 13:57:12 +01:00
Dušan Borovčanin 1df4dcd7b7 NOISSUE - Add name field for Bootstrap Config (#564)
* Add name field to Config

Enable search by name.

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

* Create separate response for unknown Configs

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

* Use meaningful names for filters

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

* Add name search tests

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

* Update API docs

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

* Break mocks check into multiple lines

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

* Create new instances in a consistent way

Reformat `return` statements.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-02-06 10:28:54 +01:00
Aleksandar Novaković 8966a13760 Fix non-SenML message routing in normalizer (#573)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-02-04 13:07:05 +01:00
Drasko DRASKOVIC 0fbb21c3fb Fix Dusan Maldenovic GitHub (#570)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-02-03 09:12:40 +01:00
Drasko DRASKOVIC 998c7ef9ef NOISSUE - Update authors list (#569)
* NOISSUE - Update authors list

Add Dejan's recognition

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

* Fix Manu's GitHub nick

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-02-02 22:21:33 +01:00
Manuel Imperiale dc16eb5451 Update lora.md (#568)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-02-01 23:46:02 +01:00
Manuel Imperiale 53036b5ae7 Fix CLI docs (#566)
* Fix CLI docs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CLI docs typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CLI docs typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CLI docs typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-02-01 23:17:09 +01:00
Manuel Imperiale c04909d645 NOISSUE- Improve LoRa doc (#562)
* Improve lora docs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-01-31 18:26:00 +01:00
Dušan Borovčanin bf9e148120 MF-551 - Add metadata fields to Bootstrap Channels (#563)
* Save MF channel name and metadata to the BS

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

* Remove separate Channels table

Use nested JSON field instead.

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

* Fix tests

Use proper JSON format for Bootstrap response fields.

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

* Update API docs

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

* Remove unnecessary comments

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

* Extract Config fields to constants

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

* Inline if statements

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-01-30 16:40:37 +01:00
Aleksandar Novaković 330545ad38 Fix MQTT adapter by setting subscription queue (#561)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-28 22:55:14 +01:00
Nikola Marčetić 1947d39119 MF-558 - Add MQTT subtopics documentation (#559)
* Added MQTT subtopic's doc section

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Resolving remarks

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Resolving remarks

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Resolved remarks

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-01-28 14:36:29 +01:00
Drasko DRASKOVIC 0b770ce49e Fix regexp for SUB (#557)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-01-26 20:53:35 +01:00
Drasko DRASKOVIC 2c6379f823 Simplify MQTT topipc regexp (#555)
Current regexp makes 2 selection groups, while
capturing just the first group (the only one of
interest) into variable.

This PR simplifies the regexp and avoids capturing
secound group.

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-01-26 19:53:01 +01:00
Nikola Marčetić c8e7a97122 MF-429 -Enabled MQTT subtopic's (#554)
* Enabled MQTT subtopic

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Removed debug logs

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Resolved remarks

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Resolved remarks

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-01-25 22:22:36 +01:00
Manuel Imperiale 6bc287e028 NOISSUE - Fix normalizer exposed port in docker-compose (#548)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-01-21 09:28:50 +01:00
Aleksandar Novaković f9f25e2d2f Fix pagination response for empty page (#547)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-17 17:31:30 +01:00
Aleksandar Novaković ca6968f8d0 Fix swagger and provisioning docs (#546)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-17 16:56:03 +01:00
Aleksandar Novaković a618627599 Add env var for number of concurrent messages (#545)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-17 02:26:58 +01:00
Aleksandar Novaković fedbe1c422 NOISSUE - Update doc and fix empty key bug (#544)
* Update docs with new connection endpoints

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

* Fix empty key bug

Return key that was absent from things that are returned in
connections endpoint.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-15 18:51:06 +01:00
Dušan Borovčanin 7ff738360b MF-370 - Simplify and refine CI (#541)
* Add CI script

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

* Fix indentetion

Add script explanation comment.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-01-11 13:57:47 +01:00
Manuel Imperiale a0505e0eeb NOISSUE - Add connection commands to CLI (#542)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-01-10 21:42:31 +01:00
Drasko DRASKOVIC 3bbd21f583 NOISSUE - Refine docs (#537)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-01-09 17:04:34 +01:00
Dušan Borovčanin c788ef64c0 Update licnese year (#533)
Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-01-09 16:23:30 +01:00
Dušan Borovčanin 22fc26b375 MF-513 - Add Bootstrapping service (#524)
* Introduce Config response for bootstrap procedure

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

* Add inital service implementation

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

* Enable status change

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

* Fix logger import

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

* Update BSS to send config in valid format

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

* Use ConfigReader to create valid format response

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

* Update config retrieval error handle

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

* Enable Thing deletion API

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

* Add API support for fetching Thing by ID

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

* Add list Things endpoint

Update database schema

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

* Use MF API to update status

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

* Use Channels list

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

* Fix reading Thing from the database

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

* Create Mainflux Thing when adding new Thing to BS

Create MF Thing as soon as Bootstrap service thing is added. There are 2
main reasons to create Thing when adding a new BS Thing over creating
Thing on bootstrapping:
1) On bootstrapping time, user JWT will not be sent as a part of
request, so there is no mechanism to send a valid API call to Mainflux.
2) This way, Bootstrap service will be in sync with Mainlux: each Thing
existing in BS will also be in Mainflux.

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

* Add Thing update

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

* Remove API key from BS service

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

* Improve channels update algorithm

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

* Refactor code

Remove unused fields, comment code and simplfy some method signatures.

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

* Remove Identity Provider and use gRPC

Update dependencies

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

* Add external auth key

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

* Update BS config reader

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

* Update docker-compose

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

* Update env variable read

Add MQTT password to bootstrap response.

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

* Update response fields and tags

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

* Remove status check

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

* Enable BS of active Things

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

* Add NewThing state

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

* Rename Status to State

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

* Update README.md

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

* Add filterng

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

* Update List endpoint

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

* Fix Database query

Remove copyright headers.

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

* Add filter type

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

* Gateway provisioning (1.d)

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

* Update self-bootstrapping feature

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

* Add mocks

Update dependencies to the newest Mainflux version.

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

* Add thing service tests

Mocks fix.
Some of the service code intentionally left untested due to possible
changes in future.
Fix copyright headers and update Mainflux and other dependencies.

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

* Use name "Config" instead of "Thing"

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

* Refactor code

Remove commented code.
Fix typo.
Remove unused exported error.

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

* Simplify service tests

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

* Remove Assign method

Raise test coverage.

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

* Update database schema

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

* Store unknown bootstrap attempts

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

* Update unknown bootstrap handling

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

* Update naming

Fix uses of `Thing` in DB and `api` package.

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

* Add endpoint tests

Currently, only test for adding a new Config are implemented.

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

* Add initialization of DB tests

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

* Add DB tests

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

* Update readme file

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

* Add API docs

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

* Remove Mainflux from vendor

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

* Add licence headers

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

* Fix service and endpoint tests

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

* Raise test coverage

Remove unused repsonse type.

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

* Update build and deployment

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

* Update API docs

Fix typo.

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

* Update imports formatting

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

* Make state response empty

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

* Raise test coverage

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

* Update API docs

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

* Update readme file

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

* Use uuid as a primary key

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

* Use Mainflux ID

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

* Remove `Created` state.

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

* Move State to separate file

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

* Add Things prefix

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

* Update API and API docs

Be consistent in API naming and add some useful comments.

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

* Refactor repository implementation

Cleanup code, make it more readable. Fix missing drop in migrations.

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

* Use "cfg" insted of "thing"

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

* Fix tests

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

* Update tables names

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2019-01-09 15:42:23 +01:00
Aleksandar Novaković 8dbe2f4066 Add dedicated env vars for event sourcing (#536)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-08 22:08:24 +01:00
Drasko DRASKOVIC 72fa9db939 NOISSUE - Fix docs (#535)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-01-08 21:15:00 +01:00
Manuel Imperiale 7a3d7fa8ec Add lora doc to getting-started.md (#529)
* Add lora doc to getting-started.md

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix 

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix doc

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add missing components doc to architecture.md (#531)

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix doc

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-01-08 12:25:13 +01:00
Aleksandar Novaković ea1993665d MF-483 - Enable channels and devices corresponding lists in backend (#520)
* Add list channels by thing id endpoint

Add list channels by thing id endpoint to things service. Add
pagination format and logic.

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

* Add fetch channels by thing endpoint

Add fetch channels by thing endpoint with pagination.

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

* Update list endpoints to contain pagination

Update list endpoints to contain pagination metadata.

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

* Add tests for two new endpoints

Add tests for two new endpoints and update existing ones. Also,
remove connected field from channel response.

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

* Fix tests for SDK

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

* Add SDK methods for new endpoints

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

* Update swagger docs for things service

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

* Add error handling to http tests

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

* Fix response body in http tests

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

* Remove unused responses from things service

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

* Add test cases to things postgres tests

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

* Add test cases for event sourcing

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-08 11:53:24 +01:00
Aleksandar Novaković bd00c5fa13 NOISSUE - Fix event sourcing client on LoRa adapter (#527)
* Fix event sourcing client on LoRa adapter

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

* Fix tests for things redis streams

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-01-07 23:54:18 -02:00
Manuel Imperiale c75aaa9d45 Add missing components doc to architecture.md (#531)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-29 22:17:35 +01:00
Aleksandar Novaković cdd2eb4bc7 Fix MQTT adapter scaling issue (#526)
MQTT adapter wasn't scalable because mq emitter wasn't setup. Now
it's working as intended. You can create two different instances
and exchange messages between their clients.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-12-27 10:44:57 +01:00
Aleksandar Novaković 584c8591ad Fix logging level in MQTT adapter (#522)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-12-21 17:45:49 +01:00
Aleksandar Novaković 643dc12408 Remove heartbeat log from MQTT broker (#521)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-12-21 16:14:54 +01:00
Manuel Imperiale 655141e941 MF-433 - Add method for reading messages from readers to SDK. (#511)
* MF-433 - Add method for reading messages from readers to SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add messages read command on CLI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-18 22:04:34 +01:00
Manuel Imperiale c8069827bb MF-506 - Fix influx & mongo readers queries to return most recent messages (#510)
* MF-506 - Fix fluxdb-reader query to return recent messages

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-506 - Fix mongodb-reader query to return messages sorted by descendent time

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix readers tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-18 15:17:55 +01:00
Aleksandar Novaković 1e017bdbcc Fix data scheme in cassandra writer (#514)
Fix data scheme so that cassandra stores channel and thing id as
text. Support sorting by time per channel partition.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-12-17 20:41:11 +01:00
Manuel Imperiale 5f6bbf4b0a MF-419 - Use JSON for CLI commands output (#504)
* MF-419 - Use JSON for CLI commands output

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* dep ensure

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove accidentaly added CLI binary

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix cast

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use logCreated for token command

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-16 14:01:37 -05:00
Michael Finley 49a41d909e MF-448 - Add option to connect to DB with verify-ca and verify-full (#500)
* MF-448 - Add option to connect to DB with verify-ca and verify-full

Adds the option to connect with verify-ca and verify-full
Users can now specify any extra certs and keys they may need.

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

* Passing db config struct rather than seperate parameters

Also updated the tests to use the config

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

* Unexporting fields apart of users config

Also added comments to the newly exported Config in things & users postgres/init.go

Signed-off-by: MichaelFinley <Michael.Finley@target.com>
2018-12-16 00:28:22 +01:00
Manuel Imperiale d1012d14c7 Add name to influx tagset (#501)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-11 08:32:10 -08:00
Drasko DRASKOVIC 8a2734d122 NOISSUE - Housekeeping. Update README. (#497)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-09 20:06:20 +01:00
Drasko DRASKOVIC 399cc051b3 NOISSUE - Fix CHANGELOG (#496)
* NOISSUE - Fix CHANGLEONG link

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

* NOISSUE - Fix CHANGELOG

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-08 21:47:19 +01:00
Drasko DRASKOVIC 179471d4d7 NOISSUE - Fix CHANGLEONG link (#495)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-08 20:58:28 +01:00
Drasko DRASKOVIC 607a0b22e0 Add CHANGELOG. Bump version. (#494)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-08 20:53:39 +01:00
Drasko DRASKOVIC f354332fa1 NOISSUE - Add changelog target to Makefile (#493)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-08 20:12:24 +01:00
Drasko DRASKOVIC 9725c2723d MF-486 - Add provisioning command to CLI (#487)
* MF-486 - Add provisioning command to CLI

Resolves #486

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

* Add error check on file open

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

* Add provision test to CLI

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

* Update error handling

Update CSV file.

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

* Fix provision for new SDK

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

* Correct structs and sprintfs

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

* Check array length

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

* Correct sprintf. Update dep lock file.

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-08 16:59:55 +01:00
Manuel Imperiale 61eed0c72d Fix lora-adapter event store handlers (#492)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-05 19:57:23 +01:00
Manuel Imperiale 715a662f0d NOISSUE - Add LoRa route map validation and fix LoRa messages URL (#491)
* Add route map validation and fix LoRa messages URL

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm private error comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add runlora command in Makefile

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-05 15:15:15 +01:00
Aleksandar Novaković b9bf63e377 MF-475 - Replace increment ID with UUID (#490)
* Update increment ID to UUID in things service

Update increment ID to UUID for things and channels in things
service and proto files. Also, update ID type from uint to string.

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

* Update increment ID to UUID in http adapter

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

* Update increment ID to UUID in ws adapter

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

* Update increment ID to UUID in CoAP adapter

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

* Update increment ID to UUID in normalizer service

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

* Update increment ID to UUID in writer services

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

* Update increment ID to UUID in reader services

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

* Update increment ID to UUID in SDK

Update increment ID to UUID in SDK. Update id type to string.
Update tests.

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

* Update increment ID to UUID in mqtt adapter

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

* Remove unnecessary case from influxdb reader

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

* Update tests in order to increase code coverage

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

* Update lora adapter to use string ID instead of unsigned int

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-12-05 13:09:25 +01:00
Manuel Imperiale 21ab52576c MF-166 - Add lora-adapter service (#481)
* MF-166 - Add lora-adapter service (#416)

* MF-166 - Add lora-adapter service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix NATS connexion and use credentials with gRPC

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Forward lora msgs to nats

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add make cmd and docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Define lora conf as private

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename funcs fix nats conn

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README and fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm NATS sub

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-166 - Add lora-adapter service  (#461)

* MF-166 - Add lora-adapter service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix NATS connexion and use credentials with gRPC

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Forward lora msgs to nats

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add make cmd and docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Define lora conf as private

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename funcs fix nats conn

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README and fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm NATS sub

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix lora server topic and logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix HTTP port

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add event sourcing client to LoRa adapter (#471)

* Add event sourcing client to LoRa adapter

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Update redis version in docker compose and update env vars

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Add ES subscription to main LoRa function

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Add new env vars to readme file of LoRa adapter

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Add message acknowledgement to LoRa adapter

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Add handling of empty values to event sourcing client (#474)

Signed-off-by: Aleksandar Novaković <anovakovic01@gmail.com>

* Add routemap and handle event sourcing

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix eventStore decoding

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv docker-compose in docker/addons

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix routemap and logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update Gopkg.toml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix route map and typos

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2018-12-04 20:08:30 +01:00
Drasko DRASKOVIC 7db3b295be NOISSUE - Add Makefile target to clean old imgs (#485)
* NOISSUE - Add Makefile target to clean old imgs

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

* Simplify docker clean commands

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-12-03 16:19:09 +01:00
Aleksandar Novaković 671c32affe MF-473 - Add metadata field to channel (#476)
* Add metadata field to channels and update things metadata

Add metadata field to channels. Update thing metadata field to be
in JSON format. Update tests..

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

* Add metadata to channel events

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

* Add metadata field to channel in go SDK

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-28 15:58:48 +01:00
Dušan Borovčanin bcadd7c47b Make CoAP ping period configurable (#469)
Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-27 14:02:30 +01:00
Aleksandar Novaković 59c5e350e1 Add nginx ingress config to k8s services (#472)
Update README in accordance to latest changes. Add gRPC load
balancing setup. Add healthcheck to k8s setup.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-27 12:56:36 +01:00
Dušan Borovčanin 6cd11536a0 Add CoAP section in getting-started (#468)
Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-27 12:46:18 +01:00
Ivan Milošević 4f01c0aa7a NOISSUE - Move CLI documentation from getting started guide to separate page (#470)
* Move CLI documentation from getting started guide to seperate page

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

* Fix cli usage examples in readme and docs

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-11-27 08:40:56 +01:00
Ivan Milošević 15667120b7 NOISSUE - Update Getting Started doc with CLI usage (#465)
* Update "getting started" docs with CLI usage

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

* Update CLI description

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

* Update CLI docs with help flag

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-11-26 12:50:32 +01:00
Dušan Borovčanin 9ede8c70b9 Update CoAP docs with URL example (#463)
Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-22 18:27:01 +01:00
Aleksandar Novaković a680fa35d2 MF-447 - Add event sourcing to things service (#460)
* Add event sourcing middleware for things service

Add event sourcing for write operations over things and channels.

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

* Add tests for event sourcing middleware

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

* Update things in tests to contain metadata field

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-20 19:46:33 +01:00
Aleksandar Novaković 8ddd78c818 Add TLS support to CoAP adapter and all readers (#459)
* Add TLS support to CoAP adapter and all readers

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

* Add new env vars to README files

Add tls env vars to README files for CoAP adapter, WS adapter
and all readers.

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

* Update all services to exit on invalid TLS env var value

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-20 12:52:28 +01:00
Ivan Milošević ee262b9647 MF-417 - Implement SDK tests (#438)
* Add SKD tests for creating channel

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

* Add SDK tests for Channel function

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

* Add list channels over SDK tests

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

* Add SDK tests for updating channel

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

* Add SDK tests for deleting channel

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

* Add users service SDK tests

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

* SDK things tests

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

* Add SDK test for connecting and disconnecting things from channel

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

* testing SDK sending messages

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

* add tests for SDK func SetContentType

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

* add all test cases for sending messages

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

* Add handling StatusBadRequest when deleting thing over SDK

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

* Update error responses when deleting channel and thing

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

* Removed unused Unauthorized response when creating user

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

* update testing CreateChannel, tests if response is some string

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

* Add bad request case in testing CreateToken

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

* Remove response error conflict from things service

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

* Fix definition of sdk.ErrFailedDisconnect and return error in test cases for disconnecting things

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

* Add handling errors and formatting code

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

* Defined new ErrFailedPublish error in SDK

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

* Add SDK test for version

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

* Delete unused http response status in sdk.DeleteChannel

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2018-11-19 21:27:01 +01:00
Dušan Borovčanin 0379eb6c0f MF-454 - Use message Time field as a time for InfluxDB points (#455)
* Use message time as Point time in InfluxDB

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

* Use actual message time

Update all Reader and Writer services to use time from the message
instead of time given from the corrseponding Writer service.

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

* Remove message time check

Messages time less than 2**28 represent time relative to the current time so Writer used to convert this to the correct value, i.e. msg.Time += time.Now(). However, this step is optional and should really be a part of the app on top of Mainflux or could be introduced with minor changes in Normalizer, Reader or Writer services, so there is no need for this to be supported out of box.

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

* Use channel and publisher as tag keys

Move all the other Message fields to the field keys.

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-18 16:42:39 +01:00
Dušan Borovčanin bf299a0143 NOISSUE - Add .dockerignore to project root (#457)
* Add .dockerignore file to project root

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

* Cleanup project root

Update run script and remove unused files from project root.

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

* Remove .dockerignore form docker dir

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-13 22:31:13 +01:00
Aleksandar Novaković ba4b166c96 Update docker-compose so that every service has debug log level (#453)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-08 21:22:59 +01:00
Aleksandar Novaković c058cf95e4 NOISSUE - Add TLS flag for Mainflux services (#452)
* Add TLS flag that will turn TLS on or off

Add TLS flag to things, http, ws and mqtt service.

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

* Update readme files with new env vars

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-11-08 19:29:58 +01:00
Nikola Marčetić bd3c24ed83 Version updated (#450)
* Version updated

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Updated load test version

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2018-11-08 10:44:12 +01:00
Michael Finley eb38c36f80 MF-448 - Option for Postgres SSL Mode (#449)
* MF-448 - Option for Postgres SSL Mode

Adds an option to choose the ssl mode when connecting to postgres.
Only supporting disable or require for now with verify-ca and verify-full to come after more discussion.

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

* Changing package name back

Accidentally changed the package name so reverting that!

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

* Adding section in getting-started for securing pgsql connections

Signed-off-by: Michael Finley <Michael.Finley@target.com>
2018-11-07 21:54:51 +01:00
Dušan Borovčanin ec0a148ad4 MF-443 Update project dependencies (#444)
* Update project dependencies

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

* Update InfluxDB and Cassandra versions

Update InfluxDB and Cassandra versions in docker-compose file as well as
images version in tests.

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

* Update gRPC version

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

* Make name first value of constraints

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-07 10:18:57 +01:00
nwest1 260df3ae40 MF-426 - Add optional MF_CA_CERTS env variable to allow GRPC client to use TLS certs (#430)
* MF-426-Add optional MF_CA_CERTS env variable to allow GRPC client to use TLS certs

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* enable things client to be configured with a ca cert path

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* MF_CA_CERTS docs for http adapter and things service, additional logging and improved error handling when setting up TLS gRPC client

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* refactor things connect to separate function

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* getting-started updates, corrected things env variable

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* clarifying ca certs default functionality

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* configuring tls termination at service endpoint

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* enable TLS configuration for users and things

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* nginx forwarding

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* go imports

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* bad logging change

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* naming specifically to the http adapter component

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* updated tls keys, slightly different grpc configuration

set localhost, users and things as subject alternative names

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* log message consistency

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* readme updates related to server ssl configuration

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* Trying to resolve confilcts

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* removing conflicting lines from docker-compose

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* adding back http-adapter configuration

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* slight readme update

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>

* readme tweaks

Signed-off-by: nwest1 <nwest1@users.noreply.github.com>
2018-11-06 21:09:17 +01:00
Dušan Borovčanin dfa766e013 MF-407 - Values of zero are being omitted (#434)
* Fix empty protobuf values

Update Normalizer service and .proto files. Reader and Writer services
needs to be updated due to message format change.

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

* Update HTTP adapter to use gogo protobuf

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

* Update Reader services API

Update API or Reader services to match Message changes due to switching to gogo/proto.

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

* Update InfluxDB services

Update InfluxDB Reader and Writer services to match new Message format.

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

* Update MongoDB services

Update MongoDB Reader and Writer services to match new message format.

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

* Update Cassandra services

Update Cassandra Reader and Writer service to match new Message format.

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

* Fix InfluxDB Reader test

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

* Update Makefile and docs accordingly

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

* Fix possible data race in InfluxDB writer

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

* Update InfluxDB Writer tests

Raise test coverage.

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

* Fix InfluxDB reader

Fix wrong ValueSum readings. Upadete tests and raise coverage.

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

* Update MongoDB services tests

Raise test coverage for MongoDB Reader and Writer services.

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

* Update Readers API tests

Raise test coverage.

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

* Raise test coverage

Update Cassandra Reader and Writer services tests.

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

* Use gogo/protobuf in CoAP adapter

Add gogo/protobuf to Gopkg.toml and update dependencies.
Update Dockerfile to run `make proto`.

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

* Update Cassandra Reader tests

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

* Refactor code

Improve code style and comments to improve readability.

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

* Rename Sum to SumValue

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

* Rename Values to Value

Since message contains only single value (or possibly no value at all),
`Values` name could be misleading. Rename simple double value from
`Value` to `FloatValue` accordingly.

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

* Fix InfluxDB Reader logging

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

* Replace exclusive if statements with switch-case

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

* Update Cassandra services tests

Signed-off-by: Dusan Borovcanin <dusan.borovcanin@mainflux.com>
2018-11-05 19:18:51 +01:00
Jovan Kostovski 8c0c994385 Expose the InfluxDB and Cassandra ports to host (#441)
* Expose the InfluxDB and Cassandra ports to host

Expose the InfluxDB's HTTP API port 8086 and  Cassandra's CQL port 9042 from the containers
to the host machine, in order to enable easier exploration of the data stored in the databases
from the host machine and development of apps which connect and use the data stored in the databases.

Signed-off-by: Jovan Kostovski <chombium@gmail.com>

* Port exposing cleanup in docker-compose.yml files

There were some doubled configurations for port exposing with the expose and ports directives in the docker-compose.yml files which had to be cleaned.

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2018-11-02 09:58:27 +01:00
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
Drasko DRASKOVIC 845b69a4ac Bump version to 0.5.1 (#385)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-09-04 23:32:04 +02:00
Aleksandar Novaković 0c77d84176 MF-382 - Improve performance by adding Redis cache for message auth (#383)
* Add redis caching for thing and channel connections

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

* Fix authorization caching flow

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

* Update things documentation

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-09-04 22:19:43 +02:00
Sava R 970c1c86ca Fix topic to channel parsing (#381)
Signed-off-by: Sava Radoš <srados@bitsfactory.com>
2018-09-04 14:07:51 +02:00
Aleksandar Novaković 902630fb14 Fix type and name switching in connected things (#376)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2018-08-29 14:28:56 +02:00
Drasko DRASKOVIC 4ca443038d Update MAINTAINERS 2018-08-28 22:42:47 +02:00
Drasko DRASKOVIC 47c56cbac9 Update MAINTAINERS (#373)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2018-08-28 22:41:25 +02:00
1365 changed files with 145108 additions and 62740 deletions
+10
View File
@@ -0,0 +1,10 @@
.git
.github
build
ui
docker
docs
k8s
load-test
metrics
scripts
+153
View File
@@ -0,0 +1,153 @@
# Docker: Environment variables in Compose
## NGINX
MF_NGINX_HTTP_PORT=80
MF_NGINX_SSL_PORT=443
MF_NGINX_MQTT_PORT=8883
## NATS
MF_NATS_URL=nats://nats:4222
## REDIS
MF_REDIS_TCP_PORT=6379
## UI
MF_UI_PORT=3000
## Grafana
MF_GRAFANA_PORT=3000
## Jaeger
MF_JAEGER_PORT=6831
MF_JAEGER_FRONTEND=16686
MF_JAEGER_COLLECTOR=14268
MF_JAEGER_CONFIGS=5778
MF_JAEGER_URL=jaeger:6831
## Core Services
### Users
MF_USERS_LOG_LEVEL=debug
MF_USERS_HTTP_PORT=8180
MF_USERS_GRPC_PORT=8181
MF_USERS_DB_PORT=5432
MF_USERS_DB_USER=mainflux
MF_USERS_DB_PASS=mainflux
MF_USERS_DB=users
MF_USERS_SECRET=secret
### Things
MF_THINGS_LOG_LEVEL=debug
MF_THINGS_HTTP_PORT=8182
MF_THINGS_AUTH_HTTP_PORT=8989
MF_THINGS_AUTH_GRPC_PORT=8183
MF_THINGS_DB_PORT=5432
MF_THINGS_DB_USER=mainflux
MF_THINGS_DB_PASS=mainflux
MF_THINGS_DB=things
MF_THINGS_SECRET=secret
### Normalizer
MF_NORMALIZER_LOG_LEVEL=debug
MF_NORMALIZER_PORT=8184
### WS
MF_WS_ADAPTER_LOG_LEVEL=debug
MF_WS_ADAPTER_PORT=8186
### HTTP
MF_HTTP_ADAPTER_PORT=8185
### MQTT
MF_MQTT_ADAPTER_LOG_LEVEL=debug
MF_MQTT_ADAPTER_PORT=1883
MF_MQTT_ADAPTER_WS_PORT=8880
### COAP
MF_COAP_ADAPTER_LOG_LEVEL=debug
MF_COAP_ADAPTER_PORT=5683
## Addons Services
### Bootstrap
MF_BOOTSTRAP_LOG_LEVEL=debug
MF_BOOTSTRAP_PORT=8200
MF_BOOTSTRAP_DB_PORT=5432
MF_BOOTSTRAP_DB_USER=mainflux
MF_BOOTSTRAP_DB_PASS=mainflux
MF_BOOTSTRAP_DB=bootstrap
MF_BOOTSTRAP_DB_SSL_MODE=disable
### LoRa
MF_LORA_ADAPTER_LOG_LEVEL=debug
MF_LORA_ADAPTER_MESSAGES_URL=tcp://lora.mqtt.mainflux.io:1883
MF_LORA_ADAPTER_HTTP_PORT=8187
### Cassandra Writer
MF_CASSANDRA_WRITER_LOG_LEVEL=debug
MF_CASSANDRA_WRITER_PORT=8902
MF_CASSANDRA_WRITER_DB_PORT=9042
MF_CASSANDRA_WRITER_DB_CLUSTER=mainflux-cassandra
MF_CASSANDRA_WRITER_DB_KEYSPACE=mainflux
### Cassandra Reader
MF_CASSANDRA_READER_LOG_LEVEL=debug
MF_CASSANDRA_READER_PORT=8903
MF_CASSANDRA_READER_DB_PORT=9042
MF_CASSANDRA_READER_DB_CLUSTER=mainflux-cassandra
MF_CASSANDRA_READER_DB_KEYSPACE=mainflux
### InfluxDB Writer
MF_INFLUX_WRITER_LOG_LEVEL=debug
MF_INFLUX_WRITER_PORT=8900
MF_INFLUX_WRITER_BATCH_SIZE=5000
MF_INFLUX_WRITER_BATCH_TIMEOUT=5
MF_INFLUX_WRITER_DB_PORT=8086
MF_INFLUX_WRITER_DB_NAME=mainflux
MF_INFLUX_WRITER_DB_USER=mainflux
MF_INFLUX_WRITER_DB_PASS=mainflux
MF_INFLUX_WRITER_GRAFANA_PORT=3001
### InfluxDB Reader
MF_INFLUX_READER_LOG_LEVEL=debug
MF_INFLUX_READER_PORT=8905
MF_INFLUX_READER_DB_NAME=mainflux
MF_INFLUX_READER_DB_PORT=8086
MF_INFLUX_READER_DB_USER=mainflux
MF_INFLUX_READER_DB_PASS=mainflux
### MongoDB Writer
MF_MONGO_WRITER_LOG_LEVEL=debug
MF_MONGO_WRITER_PORT=8901
MF_MONGO_WRITER_DB_NAME=mainflux
MF_MONGO_WRITER_DB_PORT=27017
### MongoDB Reader
MF_MONGO_READER_LOG_LEVEL=debug
MF_MONGO_READER_PORT=8904
MF_MONGO_READER_DB_NAME=mainflux
MF_MONGO_READER_DB_PORT=27017
### Postgres Writer
MF_POSTGRES_WRITER_LOG_LEVEL=debug
MF_POSTGRES_WRITER_PORT=9104
MF_POSTGRES_WRITER_DB_PORT=5432
MF_POSTGRES_WRITER_DB_USER=mainflux
MF_POSTGRES_WRITER_DB_PASS=mainflux
MF_POSTGRES_WRITER_DB_NAME=messages
MF_POSTGRES_WRITER_DB_SSL_MODE=disable
MF_POSTGRES_WRITER_DB_SSL_CERT=""
MF_POSTGRES_WRITER_DB_SSL_KEY=""
MF_POSTGRES_WRITER_DB_SSL_ROOT_CERT=""
### Postgres Reader
MF_POSTGRES_READER_LOG_LEVEL=debug
MF_POSTGRES_READER_PORT=9204
MF_POSTGRES_READER_CLIENT_TLS=false
MF_POSTGRES_READER_CA_CERTS=""
MF_POSTGRES_READER_DB_PORT=5432
MF_POSTGRES_READER_DB_USER=mainflux
MF_POSTGRES_READER_DB_PASS=mainflux
MF_POSTGRES_READER_DB_NAME=messages
MF_POSTGRES_READER_DB_SSL_MODE=disable
MF_POSTGRES_READER_DB_SSL_CERT=""
MF_POSTGRES_READER_DB_SSL_KEY=""
MF_POSTGRES_READER_DB_SSL_ROOT_CERT=""
+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
-1
View File
@@ -1,4 +1,3 @@
build
site/
+293
View File
@@ -0,0 +1,293 @@
# Mainflux Changelog
## Generation
Mainflux release notes for the latest release can be obtained via:
```
make changelog
```
Otherwise, whole log in a similar format can be observed via:
```
git log --pretty=oneline --abbrev-commit
```
## 0.9.0 - 19. JUL 2019.
### Features
- Create and push docker manifest for new release from Makefile (#794)
- MF-399 - Add open tracing support (#782)
- MF-783 - Allow access checking by a thing ID (#784)
- NOISSUE - Add authorization HTTP API to things service (#772)
- Remove cli executable from repo (#776)
- NOISSUE - Use .env vars in docker-compose (#770)
- MF-663 - enable nginx port conf from docker env (#769)
- Update docs (#766)
- NOISSUE - Remove installing non-existent package in ci (#758)
- NOISSUE - Add searchable Channels name (#754)
- MF-466 - ARM docker deployment (#756)
- Add missing Websocket.js into docker ui image (#755)
- NOISSUE - Add searchable Things name (#750)
- NOISSUE - Add certificate fields to the Bootstrap service (#752)
- Update grpc and protobuf deps in mqtt adapter (#751)
- MF-742 - Things to support single user scenario (#749)
- MF-732 - Add Postgres reader (#740)
- MF-722 - Change UUID lib (#746)
- Add performance improvement to writer filtering (#744)
- NOISSUE - Update nginx version (#748)
- MF-574 - Add missing environment variables to Cassandra writer (#745)
- NOISSUE - Add compile test to CI (#743)
- MF-708 - Assign Writer(s) to a channel (#737)
- MF-732 - Add PostgreSQL writer (#733)
- NOISSUE - Add readers pagination in SDK (#736)
- Add UI websocket open/close and send/receive (#728)
- MF-707 - Allow custom Thing key (#726)
- MF-525 - Add pagination response to the readers (#729)
- NOISSUE - Rm Things type from lora-adapter (#727)
- skip deleting of persistent volumes by default (#723)
- MF-488 - Remove Thing type (app or device) (#718)
- Remove empty channels check (#720)
- MF-655 Proper usage of docker volumes (#657)
- NOISSUE - Improve UI styling (#719)
- MF-715 - Conflict on updating connection with a valid list of channels (#716)
- MF-711 - Create separate Redis instance for ES (#717)
- NOISSUE - Update event fields naming (#713)
- MF-698 - Add missing info and docs about sys event sourcing (#712)
- MF-549 - Change metadata format from JSON string to JSON object (#706)
- NOISSUE - Replace repeating code by card gen func (#697)
- Update Bootstrap service docker-compose.yml (#700)
- Remove Debug function (#699)
- MF-687 - Add event sourcing to Bootstrap service (#695)
- NOISSUE - Remove debugging message from response of handle error function (#696)
- Add event stream to MQTT adapter for conn status (#692)
- NOISSUE - Improve UI style (#691)
- Update docs structure (#686)
- Use images instead of carousel (#685)
- NOISSUE - Update docs (#683)
- MF-662 - Change menu style (#678)
- MF-651 - X509 Mutual TLS authentication (#676)
- Update Aedes version for MQTT adapter (#677)
- MF-661 - Bootstrap pagination in UI (#672)
- Update subtopics section in documentation (#670)
- Remove default base URL value (#671)
### Bugfixes
- NOISSUE - Fix Readers logs (#735)
- NOISSUE - Fix Docker for ARM (#760)
- NOISSUE - Fix count when search by name is performed (#767)
- NOISSUE - Typo fix (#777)
- NOISSUE - Fix Postgres logs in Things service (#734)
- Fix CI with fixed plugin versions (#747)
- fix building problems (#741)
- fix docker-compose env (#775)
- Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
- Fix MQTT raw message deserialization (#753)
- fix variant option for manifest annotate (#765)
- fix to makefile for OSX/Darwin (#724)
- Fix .dockerignore file by removing index.html (#725)
- Fix things and channels metadata create and edit & remove thing type (#721)
- Fix Bootstrap service event map keys (#705)
- Fix logging in publish event callback (#694)
- Fix InfluxDB time bug (#689)
- Fix users service to work in offline mode (#795)
- fix mainflux_id parameter in bootstrap swagger (#789)
- Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
- Use errors and null packets in authorized pub/sub (#773)
- NOISSUE - Fix CoAP adapter (#779)
### Summary
https://github.com/mainflux/mainflux/milestone/10?closed=1
## 0.8.0 - 20. MAR 2019.
### Features
- MF-571 - Add Env.elm to set custom base URL (#654)
- NOISSUE Added docs about docker-compose config overriding (#653)
- MF-539 - Improve Bootstrap Service documentation (#646)
- MF-596 - Add subtopic to RawMessage (#642)
- NOISSUE - Prevent infinite loop in lora-adapter if Redis init fail (#647)
- Corrected grammar and rephrased a few sentences to read nicely (#641)
- MF-571 - Elm UI (#632)
- MF-552 - Use event sourcing to keep Bootstrap service in sync with Things service (#603)
- MF-540 - Add pagination in API responses for Bootstrap service (#575)
- MF-600 - Handle custom LoRa Server application decoder (#608)
- update docker-compose (#590)
- Update generated code (#602)
- Add generated files check (#601)
- MF-597 - Removed legacy code as not needed anymore (#598)
- NOISSUE - Added normalizer service to run script (#594)
- Changed RawMessage (#587)
- NOISSUE - fix CLI log (#581)
- MF-519 - Refine Message (#567)
- NOISSUE - Add name field for Bootstrap Config (#564)
- Fix non-SenML message routing in normalizer (#573)
- NOISSUE - Update authors list (#569)
- Update lora.md (#568)
- NOISSUE- Improve LoRa doc (#562)
- MF-551 - Add metadata fields to Bootstrap Channels (#563)
- Fix MQTT adapter by setting subscription queue (#561)
- MF-558 - Add MQTT subtopics documentation (#559)
- Fix regexp for SUB (#557)
- Simplify MQTT topipc regexp (#555)
- MF-429 -Enabled MQTT subtopic's (#554)
- Add env var for number of concurrent messages (#545)
- NOISSUE - Update doc and fix empty key bug (#544)
- MF-370 - Simplify and refine CI (#541)
- NOISSUE - Add connection commands to CLI (#542)
- NOISSUE - Refine docs (#537)
- Update licnese year (#533)
- MF-513 - Add Bootstrapping service (#524)
- Add dedicated env vars for event sourcing (#536)
- NOISSUE - Fix docs (#535)
- Add lora doc to getting-started.md (#529)
- MF-483 - Enable channels and devices corresponding lists in backend (#520)
- Add missing components doc to architecture.md (#531)
### Bugfixes
- MF-639 Split Content-Type header field on semicolon and evaluate all substrings (#644)
- MF-656 - Change bootstrap service port to 8200 (#658)
- Replace crossOrigin with relative path and fix messaging bug (#645)
- MF-579 Things & Channels returns 404 when not found or ID is malformed, not 500 (#633)
- Fix run command in dev guide (#605)
- MF-583 - Correct cmd/mongodb-reader HTTPServer log Info (#584)
- Fix Dusan Maldenovic GitHub (#570)
- Fix CLI docs (#566)
- Fix pagination response for empty page (#547)
- Fix swagger and provisioning docs (#546)
- NOISSUE - Fix event sourcing client on LoRa adapter (#527)
- Fix MQTT adapter scaling issue (#526)
- NOISSUE - Fix subtopic regex and restrict empty subtopic parts (#659)
- Fix missing css in container ui (#638)
- NOISSUE - Fix lora-adapter Object decode (#610)
- NOISSUE - Fix users logs in main.go (#577)
- NOISSUE - Fix normalizer exposed port in docker-compose (#548)
### Summary
https://github.com/mainflux/mainflux/milestone/9?closed=1
## 0.7.0 - 08. DEC 2018.
### Features
- MF-486 - Add provisioning command to CLI (#487)
- Fix lora-adapter event store handlers (#492)
- NOISSUE - Add LoRa route map validation and fix LoRa messages URL (#491)
- MF-475 - Replace increment ID with UUID (#490)
- MF-166 - Add lora-adapter service (#481)
- NOISSUE - Add Makefile target to clean old imgs (#485)
- MF-473 - Add metadata field to channel (#476)
- Make CoAP ping period configurable (#469)
- Add nginx ingress config to k8s services (#472)
- Add CoAP section in getting-started (#468)
- NOISSUE - Move CLI documentation from getting started guide to separate page (#470)
- NOISSUE - Update Getting Started doc with CLI usage (#465)
- Update CoAP docs with URL example (#463)
- MF-447 - Add event sourcing to things service (#460)
- Add TLS support to CoAP adapter and all readers (#459)
- MF-417 - Implement SDK tests (#438)
- MF-454 - Use message Time field as a time for InfluxDB points (#455)
- NOISSUE - Add .dockerignore to project root (#457)
- Update docker-compose so that every service has debug log level (#453)
- NOISSUE - Add TLS flag for Mainflux services (#452)
- MF-448 - Option for Postgres SSL Mode (#449)
- MF-443 Update project dependencies (#444)
- MF-426 - Add optional MF_CA_CERTS env variable to allow GRPC client to use TLS certs (#430)
- Expose the InfluxDB and Cassandra ports to host (#441)
- MF-374 - Bring back CoAP adapter (#413)
### Bugfixes
- gRPC Load Balancing between http-adapter and things (#387)
- MF-407 - Values of zero are being omitted (#434)
### Summary
https://github.com/mainflux/mainflux/milestone/8?closed=1
## 0.6.0 - 26. OCT 2018.
### Features
- Added Go SDK (#357)
- Updated NATS version (#412)
- Added debbug level to MFX logger (#379)
- Added Documentation for readers (#389)
- Added Redis cache to improve performance (#382)
## 0.5.1 - 05. SEP 2018.
### Features
- Improve performance by adding Redis cache (#382)
### Bugfixes
- Mixed up name and type of the things (#375)
- Fix MQTT topic (#380)
## 0.5.0 - 28. AUG 2018
### Features
- InfluxDB Reader (#311)
- Cassandra Reader (#313)
- MongoDB Reader (#344)
- MQTT Persistance via Redis (#328)
- CLI integrated into monorepo (#216)
- Normalizer logging (#333)
- WS swagger doc (#337)
- Payload renamed to Metadata (#343)
- Protobuf files added (#363)
- SPDX headers added (#325)
### Bugfixes
- Docker network for InfluxDB (#346)
- Vendor correct gRPC version (#340)
### Summary
https://github.com/mainflux/mainflux/milestone/6?closed=1
## 0.4.0 - 01. JUN 2018.
* Integrated MQTT adapter (#165 )
* Support for storing messages in MongoDB (#237)
* Support for storing messages in InfluxDB (#236)
* Use UUID PKs with auto-incremented values (#269 )
* Replaced JWT with plain string tokens in things service (#268 )
* Emit non-SenML messages (#239 )
* Support for Grafana (#296)
* Added WS Load test (#299 )
## 0.3.0 - 14. MAY 2018.
- CoAP API for message exchange (#186)
- Split `manager` service into `clients` and `users` (#266)
- Replaced ORM with raw SQL (#265)
- Setup Kubernetes (#226, #273)
- Fix docker compose (#274)
- Integrated `dashflux` into monorepo (#258)
- Integrated (*non-compatible*) `mqtt` into monorepo (#260)
## 0.2.3 - 24. APR 2018.
- Fix examples in the documentation (#243)
- Add service name in info response (#241)
- Improve code coverage in WS adapter (#242)
## 0.2.2 - 23. APR 2018.
- Setup load testing scenarios (#225)
## 0.2.1 - 22. APR 2018.
- Fixed `Content-Type` header checking (#238)
## 0.2.0 - 18. APR 2018
- Protobuf message serialization (#192)
- Websocket API for exchanging messages (#188)
- Channel & client retrieval paging (#227)
- Service instrumentation (#213)
- `go-kit` based JSON logger (#212)
- Project documentation (#218, #220)
- API tests (#211, #224)
## 0.1.2 - 18. MAR 2018.
### Bug fixes
- Fixed go lint warnings (#189)
- Compose failing startup (#185)
- Added missing service startup messages (#190)
+15 -16
View File
@@ -1,7 +1,7 @@
# Contributing to Mainflux
The following is a set of guidelines for contributing to Mainflux and its libraries, which are
hosted in the [Mainflux Organization](https://github.com/mainflux) on GitHub.
The following is a set of guidelines to contribute to Mainflux and its libraries, which are
hosted on the [Mainflux Organization](https://github.com/mainflux) on GitHub.
This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0).
By participating, you are expected to uphold this code. Please report unacceptable behavior to
@@ -9,16 +9,15 @@ By participating, you are expected to uphold this code. Please report unacceptab
## Reporting issues
Reporting issues is a great way to contribute to the project. We always appreciate a well-written,
thorough bug reports.
Reporting issues are a great way to contribute to the project. We are perpetually grateful about a well-written,
thorough bug report.
Prior to raising a new issue, check out [our issue
list](https://github.com/mainflux/mainflux/issues) to determine whether it already include the
problem you are facing.
Before raising a new issue, check [our issue
list](https://github.com/mainflux/mainflux/issues) to determine if it already contains the
problem that you are facing.
A good bug report shouldn't leave others needing to chase you up for more information. Please try to
be as detailed as possible. The following questions might serve as a template for writing a detailed
reports:
A good bug report shouldn't leave others needing to chase you for more information. Please be as detailed as possible. The following questions might serve as a template for writing a detailed
report:
- What were you trying to achieve?
- What are the expected results?
@@ -29,16 +28,16 @@ reports:
## Pull requests
Good pull requests (e.g. patches, improvements, new features) are a fantastic help. They should
remain focused in scope and avoid containing unrelated commits.
remain focused in scope and avoid unrelated commits.
**Please ask first** before embarking any significant pull request (e.g. implementing new features,
**Please ask first** before embarking on any significant pull request (e.g. implementing new features,
refactoring code etc.), otherwise you risk spending a lot of time working on something that the
maintainers might not want to merge into the project.
Please adhere to the coding conventions used throughout the project. If in doubt, consult the
[Effective Go](https://golang.org/doc/effective_go.html) style guide.
To start contributing to the project, [fork](https://help.github.com/articles/fork-a-repo/) it,
To contribute to the project, [fork](https://help.github.com/articles/fork-a-repo/) it,
clone your fork repository, and configure the remotes:
```
@@ -47,7 +46,7 @@ cd mainflux
git remote add upstream https://github.com/mainflux/mainflux.git
```
If you cloned a while ago, get the latest changes from upstream:
If your cloned repository is behind the upstream commits, then get the latest changes from upstream:
```
git checkout master
@@ -63,8 +62,8 @@ git checkout -b MF-[issue-number]
Commit your changes in logical chunks. When you are ready to commit, make sure
to write a Good Commit Message™. Consult the [Erlang's contributing guide](https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
if you're not sure what constitutes a Good Commit Message™. Use [interactive rebase](https://help.github.com/articles/about-git-rebase)
to group your commits into logical units of working before making them public.
if you're unsure of what constitutes a Good Commit Message™. Use [interactive rebase](https://help.github.com/articles/about-git-rebase)
to group your commits into logical units of work before making it public.
Note that every commit you make must be signed. By signing off your work you indicate that you
are accepting the [Developer Certificate of Origin](https://developercertificate.org/).
Generated
+409 -132
View File
@@ -3,92 +3,143 @@
[[projects]]
branch = "master"
digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm"
"winterm",
]
pruneopts = "UT"
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761"
name = "github.com/BurntSushi/toml"
packages = ["."]
pruneopts = "UT"
revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005"
version = "v0.3.1"
[[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]]
name = "github.com/asaskevich/govalidator"
packages = ["."]
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
branch = "master"
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
branch = "master"
name = "github.com/buger/jsonparser"
packages = ["."]
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:bb300c7f4b52deb1fcc65f6be624ae3dc289453824c2f1f184963dc41d941a68"
name = "github.com/cisco/senml"
packages = ["."]
revision = "448c9510575e1dd6f780cb10addbad998cf80418"
pruneopts = "UT"
revision = "910a55054e168c1122b905e3f8acdc5ff97cbec1"
[[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]]
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:a5036bc703c3994f70ced9b34e9941381aa6bd03e69a0ce538f43bf14dc8dc98"
name = "github.com/docker/docker"
packages = [
"pkg/namesgenerator",
"pkg/random",
]
pruneopts = "UT"
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
version = "v1.13.1"
[[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:184008c955d6a3226b700c13ed0e875a7a051a759618f9bccba9b5c99f17faa5"
name = "github.com/eclipse/paho.mqtt.golang"
packages = [
".",
"packets",
]
pruneopts = "UT"
revision = "36d01c2b4cbeb3d2a12063e4880ce30800af9560"
version = "v1.1.1"
[[projects]]
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "UT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:80abda6d164916248f3297f714a17b1a33e54bb5694ff283409b4d0320357b3a"
name = "github.com/go-kit/kit"
packages = [
"endpoint",
@@ -96,227 +147,269 @@
"metrics",
"metrics/internal/lv",
"metrics/prometheus",
"tracing/opentracing",
"transport/grpc",
"transport/http"
"transport/http",
]
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
version = "v0.6.0"
pruneopts = "UT"
revision = "ca4112baa34cb55091301bdc13b1420a122b1b9e"
version = "v0.7.0"
[[projects]]
digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659"
name = "github.com/go-logfmt/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
digest = "1:424f6593024cdf0f6f90cba81bc69ca98df3758525e6fb248198ef15ead603a9"
name = "github.com/go-redis/redis"
packages = [
".",
"internal",
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
"internal/proto",
"internal/singleflight",
"internal/util",
]
pruneopts = "UT"
revision = "7f89fbac80bcc62ce920b6dbc6ca60238d7725d1"
version = "v6.15.0"
[[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:348bbd07b4a983b2f811f81948c78142de09bb9fcf25284bee5e2ab3bdd0f6f9"
name = "github.com/gocql/gocql"
packages = [
".",
"internal/lru",
"internal/murmur",
"internal/streams"
"internal/streams",
]
revision = "651d6b1f343c6e168e45d119dcfe8f71e5bafb32"
pruneopts = "UT"
revision = "68ae1e384be4d7cd7df0f5b77f331759d806308e"
[[projects]]
digest = "1:bed9d72d596f94e65fff37f4d6c01398074a6bb1c3f3ceff963516bd01db6ff5"
name = "github.com/gofrs/uuid"
packages = ["."]
pruneopts = "UT"
revision = "6b08a5c5172ba18946672b49749cde22873dd7c2"
version = "v3.2.0"
[[projects]]
digest = "1:bbadccf3d3317ea03c0dac0b45b673b4b397c8f91a1d2eff550a3c51c4ad770e"
name = "github.com/gogo/protobuf"
packages = ["proto"]
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
pruneopts = "UT"
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.1"
[[projects]]
digest = "1:5ac24c0ad5dc117fb2b6d90d205c876ec599c09cccb702a36586680e09cb20b5"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
"ptypes/empty",
"ptypes/timestamp",
]
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/golang/snappy"
packages = ["."]
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
version = "v1.4.0"
[[projects]]
digest = "1:364b908b9b27b97ab838f2f6f1b1f46281fa29b978a037d72a9b1d4f6d940190"
name = "github.com/hailocab/go-hostpool"
packages = ["."]
pruneopts = "UT"
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
[[projects]]
branch = "master"
digest = "1:6727359c2ab1efe714cc87acb36be6cf15a3aae078d8ae736a17fec787dab307"
name = "github.com/hokaccha/go-prettyjson"
packages = ["."]
revision = "e6b9231a2b1cc5798b336fdbf99c16cd8ad1e0c9"
pruneopts = "UT"
revision = "f579f869bbfea48a61fbea09207cadd1aaeceb83"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:65b0a07f85f7b5cc019c26775efc278a155a5dea0a8aa617c980e8308d16bc55"
name = "github.com/influxdata/influxdb"
packages = [
"client/v2",
"models",
"pkg/escape"
"pkg/escape",
]
revision = "02d7d4f043b34ecb4e9b2dbec298c6f9450c2a32"
version = "v1.5.2"
pruneopts = "UT"
revision = "a2ba6e7654fb078f8a3f5add1f8d935df38161bd"
version = "v1.6.4"
[[projects]]
branch = "master"
digest = "1:965881afde38cce93606988d7ef9cd187b394a8a73b11a7dde379206f5e2fba3"
name = "github.com/jmoiron/sqlx"
packages = [
".",
"reflectx",
]
pruneopts = "UT"
revision = "cdf62fdf55f66fb2484fe214f89c059f0bd3f567"
[[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]]
name = "github.com/mongodb/mongo-go-driver"
packages = [
"bson",
"bson/builder",
"bson/decimal",
"bson/elements",
"bson/objectid",
"bson/parser",
"bson/parser/ast",
"core/address",
"core/auth",
"core/auth/internal/gssapi",
"core/command",
"core/compressor",
"core/connection",
"core/connstring",
"core/description",
"core/dispatch",
"core/event",
"core/option",
"core/readconcern",
"core/readpref",
"core/result",
"core/session",
"core/tag",
"core/topology",
"core/uuid",
"core/version",
"core/wiremessage",
"core/writeconcern",
"internal",
"mongo",
"mongo/aggregateopt",
"mongo/changestreamopt",
"mongo/clientopt",
"mongo/collectionopt",
"mongo/countopt",
"mongo/dbopt",
"mongo/deleteopt",
"mongo/distinctopt",
"mongo/dropcollopt",
"mongo/findopt",
"mongo/indexopt",
"mongo/insertopt",
"mongo/listcollectionopt",
"mongo/listdbopt",
"mongo/mongoopt",
"mongo/replaceopt",
"mongo/runcmdopt",
"mongo/updateopt"
]
revision = "8df56f107cdd485351eb2523992c3364114b4b2e"
version = "v0.0.10"
[[projects]]
digest = "1:4ce94f3d2074662c04677db471456feb56078244ebcfe3789e6e95901c085bc5"
name = "github.com/nats-io/go-nats"
packages = [
".",
"encoders/builtin",
"util"
"util",
]
revision = "062418ea1c2181f52dc0f954f6204370519a868b"
version = "v1.5.0"
pruneopts = "UT"
revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05"
version = "v1.6.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:c2024d8533dc702a6ef5c92d5cf1494858eff4484d8abbf794f21acd29e7ef70"
name = "github.com/opentracing/opentracing-go"
packages = [
".",
"ext",
"log",
"mocktracer",
]
pruneopts = "UT"
revision = "659c90643e714681897ec2521c60567dd21da733"
version = "v1.1.0"
[[projects]]
digest = "1:3b830e06971be8448e7c9e57c9cc15ce53ab7e8846e7b7c39110a177c7fae466"
name = "github.com/ory/dockertest"
packages = [
"docker",
@@ -342,126 +435,240 @@
"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:26663fafdea73a38075b07e8e9d82fc0056379d2be8bb4e13899e8fda7c7dd23"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/promhttp"
"prometheus/internal",
"prometheus/promhttp",
]
revision = "c5b7fccd204277076155f10851dad72b76a49317"
version = "v0.8.0"
pruneopts = "UT"
revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3"
version = "v0.9.1"
[[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:89cf776c0a621a34017c819b66b9e0f0bb076d3c5b9ca74763fb89570e140038"
name = "github.com/rubenv/sql-migrate"
packages = [
".",
"sqlparse"
"sqlparse",
]
revision = "081fe17d19ff4e2dd9f5a0c1158e6bcf74da6906"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
pruneopts = "UT"
revision = "ba2c6a7295c59448dbc195cef2f41df5163b3892"
[[projects]]
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
digest = "1:d3ffbc45dfe6929a4ba313464c2d761d96325d306eda29512da56c034db2a4dd"
name = "github.com/sony/gobreaker"
packages = ["."]
revision = "e9556a45379ef1da12e54847edb2fb3d7d566f36"
version = "0.3.0"
pruneopts = "UT"
revision = "b2a34562d02c4d5fd6645399c69e00141fb24e5a"
version = "0.4.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:c40d65817cdd41fac9aa7af8bed56927bb2d6d47e4fea566a74880f5c2b1c41e"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require"
"require",
]
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
digest = "1:3b0ac1ac6f703e6d43f4e29c97da2c9e34465c66e2e19f9828e9841d1c9279a4"
name = "github.com/uber/jaeger-client-go"
packages = [
".",
"config",
"internal/baggage",
"internal/baggage/remote",
"internal/spanlog",
"internal/throttler",
"internal/throttler/remote",
"log",
"rpcmetrics",
"thrift",
"thrift-gen/agent",
"thrift-gen/baggage",
"thrift-gen/jaeger",
"thrift-gen/sampling",
"thrift-gen/zipkincore",
"transport",
"utils",
]
pruneopts = "UT"
revision = "2f47546e3facd43297739439600bcf43f44cce5d"
version = "v2.16.0"
[[projects]]
digest = "1:c9d69a04f7fa171f50360bbcc32196b4de8ab8837ef772f6302d0140a1e3e7f6"
name = "github.com/uber/jaeger-lib"
packages = ["metrics"]
pruneopts = "UT"
revision = "0e30338a695636fe5bcf7301e8030ce8dd2a8530"
version = "v2.0.0"
[[projects]]
digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = "UT"
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e"
name = "github.com/xdg/scram"
packages = ["."]
pruneopts = "UT"
revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90"
[[projects]]
branch = "master"
digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e"
name = "github.com/xdg/stringprep"
packages = ["."]
pruneopts = "UT"
revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed"
[[projects]]
digest = "1:21f9cb6f1337c4776ed1d2d7b7ed6ffba3269ca12274c065364a5c0edee1f28b"
name = "go.mongodb.org/mongo-driver"
packages = [
"bson",
"bson/bsoncodec",
"bson/bsonrw",
"bson/bsontype",
"bson/primitive",
"event",
"internal",
"mongo",
"mongo/options",
"mongo/readconcern",
"mongo/readpref",
"mongo/writeconcern",
"tag",
"version",
"x/bsonx",
"x/bsonx/bsoncore",
"x/mongo/driver",
"x/mongo/driver/auth",
"x/mongo/driver/auth/internal/gssapi",
"x/mongo/driver/session",
"x/mongo/driver/topology",
"x/mongo/driver/uuid",
"x/network/address",
"x/network/command",
"x/network/compressor",
"x/network/connection",
"x/network/connstring",
"x/network/description",
"x/network/result",
"x/network/wiremessage",
]
pruneopts = "UT"
revision = "1c3b9b9a41eecdf056560e07b68e6407b8d598c3"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:7310f5459b88177d24e26c5ec7deb100b78ec03c0437d7a7aaec3c7eac333a55"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish",
"pbkdf2",
"ssh/terminal"
"ssh/terminal",
]
revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226"
pruneopts = "UT"
revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06"
[[projects]]
branch = "master"
digest = "1:70e155783b8dfd4f7697243384e2f361930ff0751459580769ce44d511f38b05"
name = "golang.org/x/net"
packages = [
"context",
@@ -469,27 +676,36 @@
"http2",
"http2/hpack",
"idna",
"internal/socks",
"internal/timeseries",
"trace"
"proxy",
"trace",
"websocket",
]
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",
@@ -505,33 +721,40 @@
"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:c3ad9841823db6da420a5625b367913b4ff54bbe60e8e3c98bd20e243e62e2d2"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"channelz",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/transport",
"keepalive",
"metadata",
"naming",
@@ -542,32 +765,86 @@
"stats",
"status",
"tap",
"transport"
]
revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
version = "v1.12.0"
pruneopts = "UT"
revision = "2e463a05d100327ca47ac218281906921038fd95"
version = "v1.16.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:de4bc452b049bde272d99e944aabcf218dc22df0a478a42a7107932f82430ae2"
name = "gopkg.in/ory-am/dockertest.v3"
packages = ["."]
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
version = "v3.3.0"
pruneopts = "UT"
revision = "9f1141b78507d23603f6be530587664ebb63a8d2"
version = "v3.3.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e3b07d2098253644fc2f77fede1f00fcc077a20e4f46c59466ffd61c4998bb00"
input-imports = [
"github.com/BurntSushi/toml",
"github.com/cisco/senml",
"github.com/dgrijalva/jwt-go",
"github.com/docker/docker/pkg/namesgenerator",
"github.com/dustin/go-coap",
"github.com/eclipse/paho.mqtt.golang",
"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/tracing/opentracing",
"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/gofrs/uuid",
"github.com/gogo/protobuf/proto",
"github.com/golang/protobuf/proto",
"github.com/golang/protobuf/ptypes/empty",
"github.com/gorilla/websocket",
"github.com/hokaccha/go-prettyjson",
"github.com/influxdata/influxdb/client/v2",
"github.com/jmoiron/sqlx",
"github.com/lib/pq",
"github.com/nats-io/go-nats",
"github.com/opentracing/opentracing-go",
"github.com/opentracing/opentracing-go/mocktracer",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/rubenv/sql-migrate",
"github.com/sony/gobreaker",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"github.com/uber/jaeger-client-go/config",
"go.mongodb.org/mongo-driver/bson",
"go.mongodb.org/mongo-driver/mongo",
"go.mongodb.org/mongo-driver/mongo/options",
"golang.org/x/crypto/bcrypt",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/credentials",
"google.golang.org/grpc/status",
"gopkg.in/ory-am/dockertest.v3",
]
solver-name = "gps-cdcl"
solver-version = 1
+67 -38
View File
@@ -1,22 +1,22 @@
[[constraint]]
name = "github.com/asaskevich/govalidator"
version = "9.0.0"
[[constraint]]
branch = "master"
name = "github.com/cisco/senml"
branch = "master"
[[constraint]]
name = "github.com/dgrijalva/jwt-go"
version = "3.2.0"
[[constraint]]
name = "github.com/eclipse/paho.mqtt.golang"
version = "1.1.1"
[[constraint]]
name = "github.com/fatih/color"
version = "1.7.0"
[[constraint]]
name = "github.com/go-kit/kit"
version = "0.6.0"
version = "0.7.0"
[[constraint]]
name = "github.com/go-zoo/bone"
@@ -24,31 +24,27 @@
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.1.0"
[[constraint]]
branch = "master"
name = "github.com/hokaccha/go-prettyjson"
[[constraint]]
branch = "master"
name = "github.com/lib/pq"
[[constraint]]
name = "github.com/nats-io/go-nats"
version = "1.4.0"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.8.0"
[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.2.0"
[[constraint]]
name = "github.com/hokaccha/go-prettyjson"
branch = "master"
[[constraint]]
name = "github.com/lib/pq"
branch = "master"
[[constraint]]
name = "github.com/nats-io/go-nats"
version = "1.6.0"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.9.1"
[[constraint]]
name = "github.com/sony/gobreaker"
version = "0.3.0"
version = "0.4.0"
[[constraint]]
name = "github.com/spf13/cobra"
@@ -56,35 +52,68 @@
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
version = "1.2.2"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
branch = "master"
[[constraint]]
name = "gopkg.in/ory-am/dockertest.v3"
version = "3.1.6"
version = "3.3.2"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.2.0"
version = "1.4.0"
[[constraint]]
branch = "master"
name = "github.com/rubenv/sql-migrate"
branch = "master"
[[constraint]]
name = "github.com/jmoiron/sqlx"
branch = "master"
[[constraint]]
name = "github.com/influxdata/influxdb"
version = "1.5.2"
version = "1.6.4"
[[constraint]]
name = "github.com/mongodb/mongo-go-driver"
version = "0.0.5"
[[constraint]]
branch = "master"
name = "github.com/gocql/gocql"
branch = "master"
[[constraint]]
name = "github.com/dustin/go-coap"
branch = "master"
[[constraint]]
name = "github.com/gogo/protobuf"
version = "1.1.1"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.16.0"
[[constraint]]
name = "github.com/go-redis/redis"
version = "v6.15.0"
[[constraint]]
name = "go.mongodb.org/mongo-driver"
version = "~1.0.0"
[[constraint]]
name = "github.com/BurntSushi/toml"
version = "~v0.3.1"
[[constraint]]
name = "github.com/gofrs/uuid"
version = "~v3.2.0"
[[constraint]]
name = "github.com/opentracing/opentracing-go"
version = "~v1.1.0"
[prune]
go-tests = true
+1 -1
View File
@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2015-2018 Mainflux
Copyright 2015-2019 Mainflux
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+14 -13
View File
@@ -7,7 +7,7 @@
[[drasko]]
Name = "Drasko DRASKOVIC"
Email = "drasko.draskovic@mainflux.com"
Email = "drasko@mainflux.com"
GitHub = "drasko"
# However, this role serves only in dead-lock events, or in a special and very rare cases
@@ -24,22 +24,23 @@
[maintainers]
[[aleksandar]]
Name = "Aleksandar NOVAKOVIC"
Email = "aleksandar@mainflux.com"
GitHub = "anovakovic01"
[[dusan]]
Name = "Dusan BOROVCANIN"
Email = "dusan@mainflux.com"
GitHub = "dusanb94"
[[manuel]]
Name = "Manuel IMPERIALE"
Email = "manuel.imperiale@gmail.com"
Email = "manuel@mainflux.com"
GitHub = "manuIO"
[[janko]]
Name = "Janko ISIDOROVIC"
Email = "janko.isidorovic@mainflux.com"
GitHub = "janko-isidorovic"
[[nikola]]
Name = "Nikola MARCETIC"
Email = "nikola.marcetic@mainflux.com"
Email = "nikola@mainflux.com"
GitHub = "nmarcetic"
[[dejan]]
Name = "Dejan MIJIC"
Email = "dejan.mijic@mainflux.com"
GitHub = "mijicd"
+112 -22
View File
@@ -1,66 +1,156 @@
## Copyright (c) 2015-2019
## Mainflux
##
## SPDX-License-Identifier: Apache-2.0
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 lora influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli bootstrap
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOOS ?= linux
GOARCH ?= amd64
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -ldflags "-s -w" -o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
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=$(subst docker_,,$(1)) \
--build-arg GOARCH=$(GOARCH) \
--build-arg GOARM=$(GOARM) \
--tag=mainflux/$(subst docker_,,$(1))-$(2) \
-f docker/Dockerfile .
endef
define make_docker_dev
docker build \
--no-cache \
--build-arg SVC=$(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 ui latest_manifest
clean:
rm -rf ${BUILD_DIR}
rm -rf mqtt/node_modules
cleandocker:
# Stop all containers (if running)
docker-compose -f docker/docker-compose.yml stop
# Remove mainflux containers
docker ps -f name=mainflux -aq | xargs -r docker rm
# Remove exited containers
docker ps -f name=mainflux -f status=dead -f status=exited -aq | xargs -r docker rm -v
# Remove unused images
docker images "mainflux\/*" -f dangling=true -q | xargs -r docker rmi
# Remove old mainflux images
docker images -q mainflux\/* | xargs -r docker rmi
ifdef pv
# Remove unused volumes
docker volume ls -f name=mainflux -f dangling=true -q | xargs -r docker volume rm
endif
install:
cp ${BUILD_DIR}/* $(GOBIN)
test:
GOCACHE=off go test -v -race -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
go test -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
proto:
protoc --go_out=plugins=grpc:. *.proto
protoc --gofast_out=plugins=grpc:. *.proto
$(SERVICES):
$(call compile_service,$(@))
$(DOCKERS):
$(call make_docker,$(@))
$(call make_docker,$(@),$(GOARCH))
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,$(@))
docker_ui:
$(MAKE) -C ui docker
docker_mqtt:
# MQTT Docker build must be done from root dir because it copies .proto files
ifeq ($(GOARCH), arm)
docker build --tag=mainflux/mqtt-arm -f mqtt/Dockerfile.arm .
else
docker build --tag=mainflux/mqtt-amd64 -f mqtt/Dockerfile .
endif
dockers: $(DOCKERS) docker_ui docker_mqtt
dockers_dev: $(DOCKERS_DEV)
ui:
$(MAKE) -C ui
mqtt:
cd mqtt && npm install
latest: dockers
define docker_push
for svc in $(SERVICES); do \
docker push mainflux/$$svc; \
docker push mainflux/$$svc-$(1):$(2); \
done
docker push mainflux/dashflux
docker push mainflux/mqtt
docker push mainflux/ui-$(1):$(2)
docker push mainflux/mqtt-$(1):$(2)
endef
changelog:
git log $(shell git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s"
define docker_manifest
for svc in $(SERVICES); do \
docker manifest create mainflux/$$svc:$(1) mainflux/$$svc-amd64:$(1) mainflux/$$svc-arm:$(1); \
docker manifest annotate mainflux/$$svc:$(1) mainflux/$$svc-arm:$(1) --arch arm --variant v7; \
docker manifest push mainflux/$$svc:$(1); \
done
docker manifest create mainflux/ui:$(1) mainflux/ui-amd64:$(1) mainflux/ui-arm:$(1)
docker manifest annotate mainflux/ui:$(1) mainflux/ui-arm:$(1) --arch arm --variant v7
docker manifest push mainflux/ui:$(1)
docker manifest create mainflux/mqtt:$(1) mainflux/mqtt-amd64:$(1) mainflux/mqtt-arm:$(1)
docker manifest annotate mainflux/mqtt:$(1) mainflux/mqtt-arm:$(1) --arch arm --variant v7
docker manifest push mainflux/mqtt:$(1)
endef
latest: dockers
$(call docker_push,$(GOARCH),latest)
latest_manifest:
$(call docker_manifest,latest)
release:
$(eval version = $(shell git describe --abbrev=0 --tags))
git checkout $(version)
$(MAKE) dockers
GOARCH=$(GOARCH) GOARM=$(GOARM) $(MAKE) dockers
for svc in $(SERVICES); do \
docker tag mainflux/$$svc mainflux/$$svc:$(version); \
docker push mainflux/$$svc:$(version); \
docker tag mainflux/$$svc-$(GOARCH) mainflux/$$svc-$(GOARCH):$(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)
docker tag mainflux/ui mainflux/ui-$(GOARCH):$(version)
docker tag mainflux/mqtt mainflux/mqtt-$(GOARCH):$(version)
$(call docker_push,$(GOARCH),$(version))
$(call docker_manifest,$(version))
rundev:
cd scripts && ./run.sh
run:
cd scripts && ./run.sh
docker-compose -f docker/docker-compose.yml up
runui:
$(MAKE) -C ui run
runlora:
docker-compose -f docker/docker-compose.yml up -d
docker-compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
docker-compose -f docker/addons/lora-adapter/docker-compose.yml up
+84 -9
View File
@@ -10,22 +10,31 @@
Mainflux is modern, scalable, secure open source and patent-free IoT cloud platform written in Go.
It accepts user, device, and application connections over various network protocols (i.e. HTTP,
It accepts user and thing connections over various network protocols (i.e. HTTP,
MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware
for building complex IoT solutions.
For more details, check out the [official documentation][docs].
## Features
Mainflux is member of the [Linux Foundation][lf] and an active contributor
to the [EdgeX Foundry][edgex] project. It has been made with :heart: by [Mainflux company][company],
which maintains the project and offers professional services around it.
- Protocol bridging (i.e. HTTP, MQTT, WebSocket, CoAP)
## Features
- Multi-protocol connectivity and protocol bridging (HTTP, MQTT, WebSocket and CoAP)
- Device management and provisioning
- Fine-grained access control
- Storage support (Cassandra, InfluxDB and MongoDB)
- Platform logging and instrumentation support
- Container-based deployment using [Docker][docker]
## Quickstart
- Event sourcing
- Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes]
- [LoRaWAN][lora] network integration
- SDK
- CLI
- Small memory footprint and fast execution
- Domain-driven design architecture, high-quality code and test coverage
## Install
Before proceeding, install the following prerequisites:
- [Docker](https://docs.docker.com/install/)
@@ -37,20 +46,65 @@ Once everything is installed, execute the following commands from project root:
docker-compose -f docker/docker-compose.yml up -d
```
## Contributing
This will bring up all Mainflux dockers and inter-connect them in the composition.
## Usage
Best way to quickstart using Mainflux is via CLI:
```
make cli
./build/mainflux-cli version
```
> Mainflux CLI can also be downloaded as a tarball from [offical release page][rel]
If this works, head to [official documentation][docs] to understand Mainflux provisioning and messaging.
## Documentation
Official documentation is hosted at [Mainflux Read The Docs page][docs].
Documentation is auto-generated from Markdown files in `./docs` directory.
If you spot an error or need for corrections, please let us know - or even better: send us a PR.
Additional practical information, news and tutorials can be found on the [Mainflux blog][blog].
## Authors
Main architect and BDFL of Mainflux project is [@drasko][drasko].
Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured
overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko]
helped with crafting initial implementation and continiusly work on the project evolutions.
Besides them, Mainflux is constantly improved and actively
developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava],
[@gsaleh][george], [@blokovi][iva], [@chombium][kole] and a large set of contributors.
Maintainers are listed in [MAINTAINERS](MAINTAINERS) file.
Mainflux team would like to give special thanks to [@mijicd][dejan] for his monumental work
on designing and implementing highly improved and optimized version of the platform,
and [@malidukica][dusanm] for his effort on implementing initial user interface.
## Contributing
Thank you for your interest in Mainflux and wish to contribute!
1. Take a look at our [open issues](https://github.com/mainflux/mainflux/issues).
2. Checkout the [contribution guide](.github/CONTRIBUTING.md) to learn more about our style and conventions.
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
## Community
### We're Hiring
If you are interested in working professionally on Mainflux,
please head to company's [careers page][careers] or shoot us an e-mail at <careers@mainflux.com>.
Note that the best way to grab our attention is by sending PRs :sunglasses:.
## Community
- [Google group][forum]
- [Gitter][gitter]
- [Twitter][twitter]
## License
[Apache-2.0](LICENSE)
[banner]: https://github.com/mainflux/mainflux/blob/master/docs/img/gopherBanner.jpg
[ci-badge]: https://semaphoreci.com/api/v1/mainflux/mainflux/branches/master/badge.svg
[ci-url]: https://semaphoreci.com/mainflux/mainflux
@@ -65,3 +119,24 @@ Thank you for your interest in Mainflux and wish to contribute!
[cov-url]: https://codecov.io/gh/mainflux/mainflux
[license]: https://img.shields.io/badge/license-Apache%20v2.0-blue.svg
[twitter]: https://twitter.com/mainflux
[lora]: https://lora-alliance.org/
[kubernetes]: https://kubernetes.io/
[rel]: https://github.com/mainflux/mainflux/releases
[careers]: https://www.mainflux.com/careers.html
[lf]: https://www.linuxfoundation.org/
[edgex]: https://www.edgexfoundry.org/
[company]: https://www.mainflux.com/
[blog]: https://medium.com/mainflux-iot-platform
[drasko]: https://github.com/drasko
[nikola]: https://github.com/nmarcetic
[dejan]: https://github.com/mijicd
[manu]: https://github.com/manuIO
[darko]: https://github.com/darkodraskovic
[janko]: https://github.com/janko-isidorovic
[alex]: https://github.com/anovakovic01
[dusan]: https://github.com/dusanb94
[sava]: https://github.com/srados
[george]: https://github.com/gesaleh
[iva]: https://github.com/blokovi
[kole]: https://github.com/chombium
[dusanm]: https://github.com/malidukica
+139
View File
@@ -0,0 +1,139 @@
# BOOTSTRAP SERVICE
New devices need to be configured properly and connected to the Mainflux. Bootstrap service is used in order to accomplish that. This service provides the following features:
1) Creating new Mainflux Things
2) Providing basic configuration for the newly created Things
3) Enabling/disabling Things
Pre-provisioning a new Thing is as simple as sending Configuration data to the Bootstrap service. Once the Thing is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Things. Only enabled Things can exchange messages over Mainflux. Bootstrapping does not implicitly enable Things, it has to be done manually.
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
## Thing Configuration Entity
Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Mainflux-related configuration. Mainflux config contains:
1) corresponding Mainflux Thing ID
2) corresponding Mainflux Thing key
3) list of the Mainflux channels the Thing is connected to
>Note: list of channels contains IDs of the Mainflux channels. These channels are _pre-provisioned_ on the Mainflux side and, unlike corresponding Mainflux Thing, Bootstrap service is not able to create Mainflux Channels.
Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as connecting corresponding Mainflux Thing to the given list of Channels. Configuration keeps _state_ of the Thing:
| State | What it means |
|----------|--------------------------------------------------------|
| Inactive | Thing is created, but isn't enabled |
| Active | Thing is able to communicate using Mainflux |
Switching between states `Active` and `Inactive` enables and disables Thing, respectively.
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
## 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_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | error |
| MF_BOOTSTRAP_DB_HOST | Database host address | localhost |
| MF_BOOTSTRAP_DB_PORT | Database host port | 5432 |
| MF_BOOTSTRAP_DB_USER | Database user | mainflux |
| MF_BOOTSTRAP_DB_PASS | Database password | mainflux |
| MF_BOOTSTRAP_DB | Name of the database used by the service | bootstrap |
| MF_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_BOOTSTRAP_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_BOOTSTRAP_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_BOOTSTRAP_PORT | Bootstrap service HTTP port | 8180 |
| MF_BOOTSTRAP_SERVER_CERT | Path to server certificate in pem format | |
| MF_BOOTSTRAP_SERVER_KEY | Path to server key in pem format | |
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
| MF_USERS_URL | Users service URL | localhost:8181 |
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
| MF_THINGS_ES_PASS | Things service event source password | |
| MF_THINGS_ES_DB | Things service event source database | 0 |
| MF_BOOTSTRAP_ES_URL | Bootstrap service event source URL | localhost:6379 |
| MF_BOOTSTRAP_ES_PASS | Bootstrap service event source password | |
| MF_BOOTSTRAP_ES_DB | Bootstrap service event source database | 0 |
| MF_BOOTSTRAP_INSTANCE_NAME | Bootstrap service instance name | bootstrap |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
| MF_BOOTSTRAP_THINGS_TIMEOUT | Things gRPC request timeout in seconds | 1 |
## Deployment
The service itself 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"
bootstrap:
image: mainflux/bootstrap:latest
container_name: mainflux-bootstrap
depends_on:
- bootstrap-db
restart: on-failure
ports:
- 8200:8200
environment:
MF_BOOTSTRAP_LOG_LEVEL: [Bootstrap log level]
MF_BOOTSTRAP_DB_HOST: [Database host address]
MF_BOOTSTRAP_DB_PORT: [Database host port]
MF_BOOTSTRAP_DB_USER: [Database user]
MF_BOOTSTRAP_DB_PASS: [Database password]
MF_BOOTSTRAP_DB: [Name of the database used by the service]
MF_BOOTSTRAP_DB_SSL_MODE: [SSL mode to connect to the database with]
MF_BOOTSTRAP_DB_SSL_CERT: [Path to the PEM encoded certificate file]
MF_BOOTSTRAP_DB_SSL_KEY: [Path to the PEM encoded key file]
MF_BOOTSTRAP_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
MF_BOOTSTRAP_CLIENT_TLS: [Boolean value to enable/disable client TLS]
MF_BOOTSTRAP_CA_CERTS: [Path to trusted CAs in PEM format]
MF_BOOTSTRAP_PORT: 8200
MF_BOOTSTRAP_SERVER_CERT: [String path to server cert in pem format]
MF_BOOTSTRAP_SERVER_KEY: [String path to server key in pem format]
MF_SDK_BASE_URL: [Base SDK URL for the Mainflux services]
MF_SDK_THINGS_PREFIX: [SDK prefix for Things service]
MF_USERS_URL: [Users service URL]
MF_THINGS_ES_URL: [Things service event source URL]
MF_THINGS_ES_PASS: [Things service event source password]
MF_THINGS_ES_DB: [Things service event source database]
MF_BOOTSTRAP_ES_URL: [Bootstrap service event source URL]
MF_BOOTSTRAP_ES_PASS: [Bootstrap service event source password]
MF_BOOTSTRAP_ES_DB: [Bootstrap service event source database]
MF_BOOTSTRAP_INSTANCE_NAME: [Bootstrap service instance name]
MF_JAEGER_URL: [Jaeger server URL]
MF_BOOTSTRAP_THINGS_TIMEOUT: [Things gRPC request timeout in seconds]
```
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 service
make bootstrap
# copy binary to bin
make install
# set the environment variables and run the service
MF_BOOTSTRAP_LOG_LEVEL=[Bootstrap log level] MF_BOOTSTRAP_DB_HOST=[Database host address] MF_BOOTSTRAP_DB_PORT=[Database host port] MF_BOOTSTRAP_DB_USER=[Database user] MF_BOOTSTRAP_DB_PASS=[Database password] MF_BOOTSTRAP_DB=[Name of the database used by the service] MF_BOOTSTRAP_DB_SSL_MODE=[SSL mode to connect to the database with] MF_BOOTSTRAP_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_BOOTSTRAP_DB_SSL_KEY=[Path to the PEM encoded key file] MF_BOOTSTRAP_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_BOOTSTRAP_CLIENT_TLS=[Boolean value to enable/disable client TLS] MF_BOOTSTRAP_CA_CERTS=[Path to trusted CAs in PEM format] MF_BOOTSTRAP_PORT=[Service HTTP port] MF_BOOTSTRAP_SERVER_CERT=[Path to server certificate] MF_BOOTSTRAP_SERVER_KEY=[Path to server key] MF_SDK_BASE_URL=[Base SDK URL for the Mainflux services] MF_SDK_THINGS_PREFIX=[SDK prefix for Things service] MF_USERS_URL=[Users service URL] MF_JAEGER_URL=[Jaeger server URL] MF_BOOTSTRAP_THINGS_TIMEOUT=[Things gRPC request timeout in seconds] $GOBIN/mainflux-bootstrap
```
Setting `MF_BOOTSTRAP_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Users gRPC endpoint trusting only those CAs that are provided.
## Usage
For more information about service capabilities and its usage, please check out
the [API documentation](swagger.yml).
[doc]: http://mainflux.readthedocs.io
+9
View File
@@ -0,0 +1,9 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package api contains implementation of bootstrap service HTTP API.
package api
+261
View File
@@ -0,0 +1,261 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/bootstrap"
)
func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(addReq)
if err := req.validate(); err != nil {
return nil, err
}
channels := []bootstrap.Channel{}
for _, c := range req.Channels {
channels = append(channels, bootstrap.Channel{ID: c})
}
config := bootstrap.Config{
MFThing: req.ThingID,
ExternalID: req.ExternalID,
ExternalKey: req.ExternalKey,
MFChannels: channels,
Name: req.Name,
ClientCert: req.ClientCert,
ClientKey: req.ClientKey,
CACert: req.CACert,
Content: req.Content,
}
saved, err := svc.Add(req.key, config)
if err != nil {
return nil, err
}
res := configRes{
id: saved.MFThing,
created: true,
}
return res, nil
}
}
func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateCertReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.UpdateCert(req.key, req.thingKey, req.ClientCert, req.ClientKey, req.CACert); err != nil {
return nil, err
}
res := configRes{}
return res, nil
}
}
func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(entityReq)
if err := req.validate(); err != nil {
return nil, err
}
config, err := svc.View(req.key, req.id)
if err != nil {
return nil, err
}
var channels []channelRes
for _, ch := range config.MFChannels {
channels = append(channels, channelRes{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
res := viewRes{
MFThing: config.MFThing,
MFKey: config.MFKey,
Channels: channels,
ExternalID: config.ExternalID,
ExternalKey: config.ExternalKey,
Name: config.Name,
Content: config.Content,
State: config.State,
}
return res, nil
}
}
func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateReq)
if err := req.validate(); err != nil {
return nil, err
}
config := bootstrap.Config{
MFThing: req.id,
Name: req.Name,
Content: req.Content,
}
if err := svc.Update(req.key, config); err != nil {
return nil, err
}
res := configRes{
id: config.MFThing,
created: false,
}
return res, nil
}
}
func updateConnEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateConnReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.UpdateConnections(req.key, req.id, req.Channels); err != nil {
return nil, err
}
res := configRes{
id: req.id,
created: false,
}
return res, nil
}
}
func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(listReq)
if err := req.validate(); err != nil {
return nil, err
}
page, err := svc.List(req.key, req.filter, req.offset, req.limit)
if err != nil {
return nil, err
}
switch {
case req.filter.Unknown:
res := listUnknownRes{}
for _, cfg := range page.Configs {
res.Configs = append(res.Configs, unknownRes{
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
})
}
return res, nil
default:
res := listRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
Configs: []viewRes{},
}
for _, cfg := range page.Configs {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
view := viewRes{
MFThing: cfg.MFThing,
MFKey: cfg.MFKey,
Channels: channels,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Name: cfg.Name,
Content: cfg.Content,
State: cfg.State,
}
res.Configs = append(res.Configs, view)
}
return res, nil
}
}
}
func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(entityReq)
if err := req.validate(); err != nil {
return removeRes{}, err
}
if err := svc.Remove(req.key, req.id); err != nil {
return nil, err
}
return removeRes{}, nil
}
}
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(bootstrapReq)
if err := req.validate(); err != nil {
return nil, err
}
cfg, err := svc.Bootstrap(req.key, req.id)
if err != nil {
return nil, err
}
return reader.ReadConfig(cfg)
}
}
func stateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(changeStateReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.ChangeState(req.key, req.id, req.State); err != nil {
return nil, err
}
return stateRes{}, nil
}
}
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
package api
import (
"fmt"
"time"
"github.com/mainflux/mainflux/bootstrap"
log "github.com/mainflux/mainflux/logger"
)
var _ bootstrap.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc bootstrap.Service
}
// NewLoggingMiddleware adds logging facilities to the core service.
func NewLoggingMiddleware(svc bootstrap.Service, logger log.Logger) bootstrap.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Add(key string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method add for key %s and thing %s took %s to complete", key, saved.MFThing, 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.Add(key, cfg)
}
func (lm *loggingMiddleware) View(key, id string) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view for key %s and thing %s took %s to complete", key, saved.MFThing, 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.View(key, id)
}
func (lm *loggingMiddleware) Update(key string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update for key %s and thing %s took %s to complete", key, cfg.MFThing, 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.Update(key, cfg)
}
func (lm *loggingMiddleware) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_cert for thing with key %s took %s to complete", thingKey, 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.UpdateCert(key, thingKey, clientCert, clientKey, caCert)
}
func (lm *loggingMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_connections for key %s and thing %s took %s to complete", key, id, 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.UpdateConnections(key, id, connections)
}
func (lm *loggingMiddleware) List(key string, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list for key %s and offset %d and limit %d took %s to complete", key, offset, limit, 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.List(key, filter, offset, limit)
}
func (lm *loggingMiddleware) Remove(key, id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove for key %s and thing %s took %s to complete", key, id, 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.Remove(key, id)
}
func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method bootstrap for thing with external id %s took %s to complete", externalID, 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.Bootstrap(externalKey, externalID)
}
func (lm *loggingMiddleware) ChangeState(key, id string, state bootstrap.State) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method change_state for key %s and thing %s took %s to complete", key, id, 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.ChangeState(key, id, state)
}
func (lm *loggingMiddleware) UpdateChannelHandler(channel bootstrap.Channel) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_channel_handler for channel %s took %s to complete", channel.ID, 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.UpdateChannelHandler(channel)
}
func (lm *loggingMiddleware) RemoveConfigHandler(id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_config_handler for config %s took %s to complete", id, 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.RemoveConfigHandler(id)
}
func (lm *loggingMiddleware) RemoveChannelHandler(id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_channel_handler for channel %s took %s to complete", id, 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.RemoveChannelHandler(id)
}
func (lm *loggingMiddleware) DisconnectThingHandler(channelID, thingID string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method disconnect_thing_handler for channel %s and thing %s took %s to complete", channelID, thingID, 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.DisconnectThingHandler(channelID, thingID)
}
+152
View File
@@ -0,0 +1,152 @@
//
// 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/bootstrap"
)
var _ bootstrap.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc bootstrap.Service
}
// MetricsMiddleware instruments core service by tracking request count and
// latency.
func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency metrics.Histogram) bootstrap.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (mm *metricsMiddleware) Add(key string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "add").Add(1)
mm.latency.With("method", "add").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Add(key, cfg)
}
func (mm *metricsMiddleware) View(id, key string) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "view").Add(1)
mm.latency.With("method", "view").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.View(id, key)
}
func (mm *metricsMiddleware) Update(key string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update").Add(1)
mm.latency.With("method", "update").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Update(key, cfg)
}
func (mm *metricsMiddleware) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_cert").Add(1)
mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateCert(key, thingKey, clientCert, clientKey, caCert)
}
func (mm *metricsMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_connections").Add(1)
mm.latency.With("method", "update_connections").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateConnections(key, id, connections)
}
func (mm *metricsMiddleware) List(key string, filter bootstrap.Filter, offset, limit uint64) (saved bootstrap.ConfigsPage, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "list").Add(1)
mm.latency.With("method", "list").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.List(key, filter, offset, limit)
}
func (mm *metricsMiddleware) Remove(id, key string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "remove").Add(1)
mm.latency.With("method", "remove").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Remove(id, key)
}
func (mm *metricsMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "bootstrap").Add(1)
mm.latency.With("method", "bootstrap").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Bootstrap(externalKey, externalID)
}
func (mm *metricsMiddleware) ChangeState(id, key string, state bootstrap.State) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "change_state").Add(1)
mm.latency.With("method", "change_state").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.ChangeState(id, key, state)
}
func (mm *metricsMiddleware) UpdateChannelHandler(channel bootstrap.Channel) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_channel").Add(1)
mm.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateChannelHandler(channel)
}
func (mm *metricsMiddleware) RemoveConfigHandler(id string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "remove_config").Add(1)
mm.latency.With("method", "remove_config").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveConfigHandler(id)
}
func (mm *metricsMiddleware) RemoveChannelHandler(id string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "remove_channel").Add(1)
mm.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveChannelHandler(id)
}
func (mm *metricsMiddleware) DisconnectThingHandler(channelID, thingID string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "disconnect_thing_handler").Add(1)
mm.latency.With("method", "disconnect_thing_handler").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.DisconnectThingHandler(channelID, thingID)
}
+172
View File
@@ -0,0 +1,172 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import "github.com/mainflux/mainflux/bootstrap"
type apiReq interface {
validate() error
}
type addReq struct {
key string
ThingID string `json:"thing_id"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"`
Channels []string `json:"channels"`
Name string `json:"name"`
Content string `json:"content"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req addReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.ExternalID == "" || req.ExternalKey == "" {
return bootstrap.ErrMalformedEntity
}
return nil
}
type entityReq struct {
key string
id string
}
func (req entityReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.id == "" {
return bootstrap.ErrMalformedEntity
}
return nil
}
type updateReq struct {
key string
id string
Name string `json:"name"`
Content string `json:"content"`
}
func (req updateReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.id == "" {
return bootstrap.ErrMalformedEntity
}
return nil
}
type updateCertReq struct {
key string
thingKey string
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req updateCertReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.thingKey == "" {
return bootstrap.ErrNotFound
}
return nil
}
type updateConnReq struct {
key string
id string
Channels []string `json:"channels"`
}
func (req updateConnReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.id == "" {
return bootstrap.ErrMalformedEntity
}
return nil
}
type listReq struct {
key string
filter bootstrap.Filter
offset uint64
limit uint64
}
func (req listReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.limit == 0 || req.limit > maxLimit {
return bootstrap.ErrMalformedEntity
}
return nil
}
type bootstrapReq struct {
key string
id string
}
func (req bootstrapReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.id == "" {
return bootstrap.ErrMalformedEntity
}
return nil
}
type changeStateReq struct {
key string
id string
State bootstrap.State `json:"state"`
}
func (req changeStateReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.id == "" {
return bootstrap.ErrMalformedEntity
}
if req.State != bootstrap.Inactive &&
req.State != bootstrap.Active {
return bootstrap.ErrMalformedEntity
}
return nil
}
+297
View File
@@ -0,0 +1,297 @@
package api
import (
"fmt"
"testing"
"github.com/mainflux/mainflux/bootstrap"
"github.com/stretchr/testify/assert"
)
func TestAddReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
externalID string
externalKey string
err error
}{
{
desc: "empty key",
key: "",
externalID: "external-id",
externalKey: "external-key",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty external ID",
key: "key",
externalID: "",
externalKey: "external-key",
err: bootstrap.ErrMalformedEntity,
},
{
desc: "empty external key",
key: "key",
externalID: "external-id",
externalKey: "",
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := addReq{
key: tc.key,
ExternalID: tc.externalID,
ExternalKey: tc.externalKey,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestEntityReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
id string
err error
}{
{
desc: "empty key",
key: "",
id: "id",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty id",
key: "key",
id: "",
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := entityReq{
key: tc.key,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
id string
err error
}{
{
desc: "empty key",
key: "",
id: "id",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty id",
key: "key",
id: "",
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := updateReq{
key: tc.key,
id: tc.id,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCertReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
thingKey string
err error
}{
{
desc: "empty key",
key: "",
thingKey: "thingKey",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty thing key",
key: "key",
thingKey: "",
err: bootstrap.ErrNotFound,
},
}
for _, tc := range cases {
req := updateCertReq{
key: tc.key,
thingKey: tc.thingKey,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
id string
err error
}{
{
desc: "empty key",
key: "",
id: "id",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty id",
key: "key",
id: "",
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := updateReq{
key: tc.key,
id: tc.id,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestListReqValidation(t *testing.T) {
cases := []struct {
desc string
offset uint64
key string
limit uint64
err error
}{
{
desc: "empty key",
key: "",
offset: 0,
limit: 1,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "too large limit",
key: "key",
offset: 0,
limit: maxLimit + 1,
err: bootstrap.ErrMalformedEntity,
},
{
desc: "zero limit",
key: "key",
offset: 0,
limit: 0,
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := listReq{
key: tc.key,
offset: tc.offset,
limit: tc.limit,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestBootstrapReqValidation(t *testing.T) {
cases := []struct {
desc string
externKey string
externID string
err error
}{
{
desc: "empty external key",
externKey: "",
externID: "id",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty external id",
externKey: "key",
externID: "",
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := bootstrapReq{
id: tc.externID,
key: tc.externKey,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestChangeStateReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
id string
state bootstrap.State
err error
}{
{
desc: "empty key",
key: "",
id: "id",
state: bootstrap.State(1),
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty id",
key: "key",
id: "",
state: bootstrap.State(0),
err: bootstrap.ErrMalformedEntity,
},
{
desc: "invalid state",
key: "key",
id: "id",
state: bootstrap.State(14),
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
req := changeStateReq{
key: tc.key,
id: tc.id,
State: tc.state,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+148
View File
@@ -0,0 +1,148 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"fmt"
"net/http"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
)
var (
_ mainflux.Response = (*removeRes)(nil)
_ mainflux.Response = (*configRes)(nil)
_ mainflux.Response = (*stateRes)(nil)
_ mainflux.Response = (*viewRes)(nil)
_ mainflux.Response = (*listRes)(nil)
)
type removeRes struct{}
func (res removeRes) Code() int {
return http.StatusNoContent
}
func (res removeRes) Headers() map[string]string {
return map[string]string{}
}
func (res removeRes) Empty() bool {
return true
}
type configRes struct {
id string
created bool
}
func (res configRes) Code() int {
if res.created {
return http.StatusCreated
}
return http.StatusOK
}
func (res configRes) Headers() map[string]string {
if res.created {
return map[string]string{
"Location": fmt.Sprintf("/things/configs/%s", res.id),
}
}
return map[string]string{}
}
func (res configRes) Empty() bool {
return true
}
type channelRes struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
}
type viewRes struct {
MFThing string `json:"mainflux_id,omitempty"`
MFKey string `json:"mainflux_key,omitempty"`
Channels []channelRes `json:"mainflux_channels,omitempty"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key,omitempty"`
Content string `json:"content,omitempty"`
Name string `json:"name,omitempty"`
State bootstrap.State `json:"state"`
}
func (res viewRes) Code() int {
return http.StatusOK
}
func (res viewRes) Headers() map[string]string {
return map[string]string{}
}
func (res viewRes) Empty() bool {
return false
}
type unknownRes struct {
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key,omitempty"`
}
type listUnknownRes struct {
Configs []unknownRes `json:"configs"`
}
func (res listUnknownRes) Code() int {
return http.StatusOK
}
func (res listUnknownRes) Headers() map[string]string {
return map[string]string{}
}
func (res listUnknownRes) Empty() bool {
return false
}
type listRes struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Configs []viewRes `json:"configs"`
}
func (res listRes) Code() int {
return http.StatusOK
}
func (res listRes) Headers() map[string]string {
return map[string]string{}
}
func (res listRes) Empty() bool {
return false
}
type stateRes struct{}
func (res stateRes) Code() int {
return http.StatusOK
}
func (res stateRes) Headers() map[string]string {
return map[string]string{}
}
func (res stateRes) Empty() bool {
return true
}
+352
View File
@@ -0,0 +1,352 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strconv"
"strings"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
contentType = "application/json"
maxLimit = 100
defaultLimit = 10
)
var (
errUnsupportedContentType = errors.New("unsupported content type")
errInvalidQueryParams = errors.New("invalid query params")
fullMatch = []string{"state", "external_id", "mainflux_id", "mainflux_key"}
partialMatch = []string{"name"}
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(encodeError),
}
r := bone.New()
r.Post("/things/configs", kithttp.NewServer(
addEndpoint(svc),
decodeAddRequest,
encodeResponse,
opts...))
r.Get("/things/configs/:id", kithttp.NewServer(
viewEndpoint(svc),
decodeEntityRequest,
encodeResponse,
opts...))
r.Put("/things/configs/:id", kithttp.NewServer(
updateEndpoint(svc),
decodeUpdateRequest,
encodeResponse,
opts...))
r.Put("/things/configs/certs/:key", kithttp.NewServer(
updateCertEndpoint(svc),
decodeUpdateCertRequest,
encodeResponse,
opts...))
r.Put("/things/configs/connections/:id", kithttp.NewServer(
updateConnEndpoint(svc),
decodeUpdateConnRequest,
encodeResponse,
opts...))
r.Get("/things/configs", kithttp.NewServer(
listEndpoint(svc),
decodeListRequest,
encodeResponse,
opts...))
r.Get("/things/unknown/configs", kithttp.NewServer(
listEndpoint(svc),
decodeUnknownRequest,
encodeResponse,
opts...))
r.Get("/things/bootstrap/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader),
decodeBootstrapRequest,
encodeResponse,
opts...))
r.Put("/things/state/:id", kithttp.NewServer(
stateEndpoint(svc),
decodeStateRequest,
encodeResponse,
opts...))
r.Delete("/things/configs/:id", kithttp.NewServer(
removeEndpoint(svc),
decodeEntityRequest,
encodeResponse,
opts...))
r.GetFunc("/version", mainflux.Version("bootstrap"))
r.Handle("/metrics", promhttp.Handler())
return r
}
func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := addReq{key: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := updateReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := updateCertReq{key: r.Header.Get("Authorization")}
req.thingKey = bone.GetValue(r, "key")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := updateConnReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUnknownRequest(_ context.Context, r *http.Request) (interface{}, error) {
q, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return nil, errInvalidQueryParams
}
offset, limit, err := parsePagePrams(q)
if err != nil {
return nil, err
}
req := listReq{
key: r.Header.Get("Authorization"),
filter: bootstrap.Filter{Unknown: true},
offset: offset,
limit: limit,
}
return req, nil
}
func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error) {
q, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return nil, errInvalidQueryParams
}
offset, limit, err := parsePagePrams(q)
if err != nil {
return nil, err
}
filter := parseFilter(q)
req := listReq{
key: r.Header.Get("Authorization"),
filter: filter,
offset: offset,
limit: limit,
}
return req, nil
}
func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := bootstrapReq{
id: bone.GetValue(r, "external_id"),
key: r.Header.Get("Authorization"),
}
return req, nil
}
func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := changeStateReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeEntityRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := entityReq{
key: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"),
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
if ar, ok := response.(mainflux.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", contentType)
switch err {
case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType)
case errInvalidQueryParams, bootstrap.ErrMalformedEntity:
w.WriteHeader(http.StatusBadRequest)
case bootstrap.ErrNotFound:
w.WriteHeader(http.StatusNotFound)
case bootstrap.ErrUnauthorizedAccess:
w.WriteHeader(http.StatusForbidden)
case bootstrap.ErrConflict:
w.WriteHeader(http.StatusConflict)
case bootstrap.ErrThings:
w.WriteHeader(http.StatusServiceUnavailable)
case io.EOF:
w.WriteHeader(http.StatusBadRequest)
default:
switch err.(type) {
case *json.SyntaxError:
w.WriteHeader(http.StatusBadRequest)
case *json.UnmarshalTypeError:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
}
}
func parseUint(s string) (uint64, error) {
if s == "" {
return 0, nil
}
ret, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, errInvalidQueryParams
}
return ret, nil
}
func parsePagePrams(q url.Values) (uint64, uint64, error) {
offset, err := parseUint(q.Get("offset"))
q.Del("offset")
if err != nil {
return 0, 0, err
}
limit, err := parseUint(q.Get("limit"))
q.Del("limit")
if err != nil {
return 0, 0, err
}
if limit > maxLimit {
limit = maxLimit
}
if limit == 0 {
limit = defaultLimit
}
return offset, limit, nil
}
func parseFilter(values url.Values) bootstrap.Filter {
ret := bootstrap.Filter{
FullMatch: make(map[string]string),
PartialMatch: make(map[string]string),
}
for k := range values {
if contains(fullMatch, k) {
ret.FullMatch[k] = values.Get(k)
}
if contains(partialMatch, k) {
ret.PartialMatch[k] = strings.ToLower(values.Get(k))
}
}
return ret
}
func contains(l []string, s string) bool {
for _, v := range l {
if v == s {
return true
}
}
return false
}
+113
View File
@@ -0,0 +1,113 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
// Config represents Configuration entity. It wraps information about external entity
// as well as info about corresponding Mainflux entities.
// MFThing represents corresponding Mainflux Thing ID.
// MFKey is key of corresponding Mainflux Thing.
// MFChannels is a list of Mainflux Channels corresponding Mainflux Thing connects to.
type Config struct {
MFThing string
Owner string
Name string
ClientCert string
ClientKey string
CACert string
MFKey string
MFChannels []Channel
ExternalID string
ExternalKey string
Content string
State State
}
// Channel represents Mainflux channel corresponding Mainflux Thing is connected to.
type Channel struct {
ID string
Name string
Metadata map[string]interface{}
}
// Filter is used for the search filters.
type Filter struct {
Unknown bool
FullMatch map[string]string
PartialMatch map[string]string
}
// ConfigsPage contains page related metadata as well as list of Configs that
// belong to this page.
type ConfigsPage struct {
Total uint64
Offset uint64
Limit uint64
Configs []Config
}
// ConfigRepository specifies a Config persistence API.
type ConfigRepository interface {
// Save persists the Config. Successful operation is indicated by non-nil
// error response.
Save(Config, []string) (string, error)
// RetrieveByID retrieves the Config having the provided identifier, that is owned
// by the specified user.
RetrieveByID(string, string) (Config, error)
// RetrieveAll retrieves a subset of Configs that are owned
// by the specific user, with given filter parameters.
RetrieveAll(string, Filter, uint64, uint64) ConfigsPage
// RetrieveByExternalID returns Config for given external ID.
RetrieveByExternalID(string, string) (Config, error)
// Update updates an existing Config. A non-nil error is returned
// to indicate operation failure.
Update(Config) error
// UpdateCerts updates an existing Config certificate and key.
// A non-nil error is returned to indicate operation failure.
UpdateCert(string, string, string, string, string) error
// UpdateConnections updates a list of Channels the Config is connected to
// adding new Channels if needed.
UpdateConnections(string, string, []Channel, []string) error
// Remove removes the Config having the provided identifier, that is owned
// by the specified user.
Remove(string, string) error
// ChangeState changes of the Config, that is owned by the specific user.
ChangeState(string, string, State) error
// SaveUnknown saves Thing which unsuccessfully bootstrapped.
SaveUnknown(string, string) error
// RetrieveUnknown returns a subset of unsuccessfully bootstrapped Things.
RetrieveUnknown(uint64, uint64) ConfigsPage
// ListExisting retrieves those channels from the given list that exist in DB.
ListExisting(string, []string) ([]Channel, error)
// Methods RemoveThing, UpdateChannel, and RemoveChannel are related to
// event sourcing. That's why these methods surpass ownership check.
// RemoveThing removes Config of the Thing with the given ID.
RemoveThing(string) error
// UpdateChannel updates channel with the given ID.
UpdateChannel(Channel) error
// RemoveChannel removes channel with the given ID.
RemoveChannel(string) error
// DisconnectHandler changes state of the Config when the corresponding Thing is
// disconnected from the Channel.
DisconnectThing(string, string) error
}
+10
View File
@@ -0,0 +1,10 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package bootstrap contains the domain concept definitions needed to support
// Mainflux bootstrap service functionality.
package bootstrap
+368
View File
@@ -0,0 +1,368 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package mocks
import (
"sort"
"strconv"
"strings"
"sync"
"github.com/mainflux/mainflux/bootstrap"
)
const (
emptyState = -1
notFoundIdx = -1
)
var _ bootstrap.ConfigRepository = (*configRepositoryMock)(nil)
type configRepositoryMock struct {
mu sync.Mutex
counter uint64
configs map[string]bootstrap.Config
channels map[string]bootstrap.Channel
unknown map[string]string
}
// NewConfigsRepository creates in-memory config repository.
func NewConfigsRepository(unknown map[string]string) bootstrap.ConfigRepository {
return &configRepositoryMock{
configs: make(map[string]bootstrap.Config),
channels: make(map[string]bootstrap.Channel),
unknown: unknown,
}
}
func (crm *configRepositoryMock) Save(config bootstrap.Config, connections []string) (string, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
for _, v := range crm.configs {
if v.MFThing == config.MFThing || v.ExternalID == config.ExternalID {
return "", bootstrap.ErrConflict
}
}
crm.counter++
config.MFThing = strconv.FormatUint(crm.counter, 10)
crm.configs[config.MFThing] = config
for _, ch := range config.MFChannels {
crm.channels[ch.ID] = ch
}
config.MFChannels = []bootstrap.Channel{}
for _, ch := range connections {
config.MFChannels = append(config.MFChannels, crm.channels[ch])
}
crm.configs[config.MFThing] = config
delete(crm.unknown, config.ExternalID)
return config.MFThing, nil
}
func (crm *configRepositoryMock) RetrieveByID(key, id string) (bootstrap.Config, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
c, ok := crm.configs[id]
if !ok {
return bootstrap.Config{}, bootstrap.ErrNotFound
}
if c.Owner != key {
return bootstrap.Config{}, bootstrap.ErrUnauthorizedAccess
}
return c, nil
}
func (crm *configRepositoryMock) RetrieveAll(key string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
crm.mu.Lock()
defer crm.mu.Unlock()
configs := make([]bootstrap.Config, 0)
if offset < 0 || limit <= 0 {
return bootstrap.ConfigsPage{}
}
first := uint64(offset) + 1
last := first + uint64(limit)
var state bootstrap.State = emptyState
var name string
if s, ok := filter.FullMatch["state"]; ok {
val, _ := strconv.Atoi(s)
state = bootstrap.State(val)
}
if s, ok := filter.PartialMatch["name"]; ok {
name = strings.ToLower(s)
}
var total uint64
for _, v := range crm.configs {
id, _ := strconv.ParseUint(v.MFThing, 10, 64)
if (state == emptyState || v.State == state) &&
(name == "" || strings.Index(strings.ToLower(v.Name), name) != notFoundIdx) &&
v.Owner == key {
if id >= first && id < last {
configs = append(configs, v)
}
total++
}
}
sort.SliceStable(configs, func(i, j int) bool {
return configs[i].MFThing < configs[j].MFThing
})
return bootstrap.ConfigsPage{
Total: total,
Offset: offset,
Limit: limit,
Configs: configs,
}
}
func (crm *configRepositoryMock) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
for _, cfg := range crm.configs {
if cfg.ExternalID == externalID && cfg.ExternalKey == externalKey {
return cfg, nil
}
}
return bootstrap.Config{}, bootstrap.ErrNotFound
}
func (crm *configRepositoryMock) Update(config bootstrap.Config) error {
crm.mu.Lock()
defer crm.mu.Unlock()
cfg, ok := crm.configs[config.MFThing]
if !ok || cfg.Owner != config.Owner {
return bootstrap.ErrNotFound
}
cfg.Name = config.Name
cfg.Content = config.Content
crm.configs[config.MFThing] = cfg
return nil
}
func (crm *configRepositoryMock) UpdateCert(owner, thingKey, clientCert, clientKey, caCert string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
var forUpdate bootstrap.Config
for _, v := range crm.configs {
if v.MFKey == thingKey && v.Owner == owner {
forUpdate = v
break
}
}
if _, ok := crm.configs[forUpdate.MFThing]; !ok {
return bootstrap.ErrNotFound
}
forUpdate.ClientCert = clientCert
forUpdate.ClientKey = clientKey
forUpdate.CACert = caCert
crm.configs[forUpdate.MFThing] = forUpdate
return nil
}
func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
config, ok := crm.configs[id]
if !ok {
return bootstrap.ErrNotFound
}
for _, ch := range channels {
crm.channels[ch.ID] = ch
}
config.MFChannels = []bootstrap.Channel{}
for _, conn := range connections {
ch, ok := crm.channels[conn]
if !ok {
return bootstrap.ErrNotFound
}
config.MFChannels = append(config.MFChannels, ch)
}
crm.configs[id] = config
return nil
}
func (crm *configRepositoryMock) Remove(key, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
for k, v := range crm.configs {
if v.Owner == key && k == id {
delete(crm.configs, k)
break
}
}
return nil
}
func (crm *configRepositoryMock) ChangeState(key, id string, state bootstrap.State) error {
crm.mu.Lock()
defer crm.mu.Unlock()
config, ok := crm.configs[id]
if !ok {
return bootstrap.ErrNotFound
}
if config.Owner != key {
return bootstrap.ErrUnauthorizedAccess
}
config.State = state
crm.configs[id] = config
return nil
}
func (crm *configRepositoryMock) RetrieveUnknown(offset, limit uint64) bootstrap.ConfigsPage {
crm.mu.Lock()
defer crm.mu.Unlock()
configs := []bootstrap.Config{}
i := uint64(0)
l := int(limit)
var keys []string
for k := range crm.unknown {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if i >= offset && len(configs) < l {
configs = append(configs, bootstrap.Config{
ExternalID: k,
ExternalKey: crm.unknown[k],
})
}
i++
}
return bootstrap.ConfigsPage{
Total: uint64(len(crm.unknown)),
Offset: offset,
Limit: limit,
Configs: configs,
}
}
func (crm *configRepositoryMock) RemoveUnknown(key, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
for k, v := range crm.unknown {
if k == id && v == key {
delete(crm.unknown, k)
return nil
}
}
return nil
}
func (crm *configRepositoryMock) SaveUnknown(key, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
crm.unknown[id] = key
return nil
}
func (crm *configRepositoryMock) ListExisting(key string, connections []string) ([]bootstrap.Channel, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
var ret []bootstrap.Channel
for k, v := range crm.channels {
for _, conn := range connections {
if conn == k {
ret = append(ret, v)
break
}
}
}
return ret, nil
}
func (crm *configRepositoryMock) RemoveThing(id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
delete(crm.configs, id)
return nil
}
func (crm *configRepositoryMock) UpdateChannel(ch bootstrap.Channel) error {
crm.mu.Lock()
defer crm.mu.Unlock()
channel, ok := crm.channels[ch.ID]
if !ok {
return nil
}
channel.Name = ch.Name
channel.Metadata = ch.Metadata
crm.channels[ch.ID] = channel
return nil
}
func (crm *configRepositoryMock) RemoveChannel(id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
delete(crm.channels, id)
return nil
}
func (crm *configRepositoryMock) DisconnectThing(channelID, thingID string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
idx := -1
if config, ok := crm.configs[thingID]; ok {
for i, ch := range config.MFChannels {
if ch.ID == channelID {
idx = i
break
}
}
if idx != -1 {
config.MFChannels = append(config.MFChannels[0:idx], config.MFChannels[idx:]...)
}
crm.configs[thingID] = config
}
delete(crm.channels, channelID)
return nil
}
+217
View File
@@ -0,0 +1,217 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package mocks
import (
"context"
"strconv"
"sync"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/things"
)
var _ things.Service = (*mainfluxThings)(nil)
type mainfluxThings struct {
mu sync.Mutex
counter uint64
things map[string]things.Thing
channels map[string]things.Channel
users mainflux.UsersServiceClient
connections map[string][]string
}
// NewThingsService returns Mainflux Things service mock.
// Only methods used by SDK are mocked.
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, users mainflux.UsersServiceClient) things.Service {
return &mainfluxThings{
things: things,
channels: channels,
users: users,
connections: make(map[string][]string),
}
}
func (svc *mainfluxThings) AddThing(_ context.Context, owner string, thing things.Thing) (things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
}
svc.counter++
thing.Owner = userID.Value
thing.ID = strconv.FormatUint(svc.counter, 10)
thing.Key = thing.ID
svc.things[thing.ID] = thing
return thing, nil
}
func (svc *mainfluxThings) ViewThing(_ context.Context, owner, id string) (things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
}
if t, ok := svc.things[id]; ok && t.Owner == userID.Value {
return t, nil
}
return things.Thing{}, things.ErrNotFound
}
func (svc *mainfluxThings) Connect(_ context.Context, owner, chanID, thingID string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
if svc.channels[chanID].Owner != userID.Value {
return things.ErrNotFound
}
svc.connections[chanID] = append(svc.connections[chanID], thingID)
return nil
}
func (svc *mainfluxThings) Disconnect(_ context.Context, owner, chanID, thingID string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil || svc.channels[chanID].Owner != userID.Value {
return things.ErrUnauthorizedAccess
}
ids := svc.connections[chanID]
i := 0
for _, t := range ids {
if t == thingID {
break
}
i++
}
if i == len(ids) {
return things.ErrNotFound
}
var tmp []string
if i != len(ids)-2 {
tmp = ids[i+1:]
}
ids = append(ids[:i], tmp...)
svc.connections[chanID] = ids
return nil
}
func (svc *mainfluxThings) RemoveThing(_ context.Context, owner, id string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
if t, ok := svc.things[id]; !ok || t.Owner != userID.Value {
return things.ErrNotFound
}
delete(svc.things, id)
conns := make(map[string][]string)
for k, v := range svc.connections {
idx := findIndex(v, id)
if idx != -1 {
var tmp []string
if idx != len(v)-2 {
tmp = v[idx+1:]
}
conns[k] = append(v[:idx], tmp...)
}
}
svc.connections = conns
return nil
}
func (svc *mainfluxThings) ViewChannel(_ context.Context, owner, id string) (things.Channel, error) {
if c, ok := svc.channels[id]; ok {
return c, nil
}
return things.Channel{}, things.ErrNotFound
}
func (svc *mainfluxThings) UpdateThing(context.Context, string, things.Thing) error {
panic("not implemented")
}
func (svc *mainfluxThings) UpdateKey(context.Context, string, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListThings(context.Context, string, uint64, uint64, string) (things.ThingsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannelsByThing(context.Context, string, string, uint64, uint64) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) ListThingsByChannel(context.Context, string, string, uint64, uint64) (things.ThingsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) CreateChannel(context.Context, string, things.Channel) (things.Channel, error) {
panic("not implemented")
}
func (svc *mainfluxThings) UpdateChannel(context.Context, string, things.Channel) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannels(context.Context, string, uint64, uint64, string) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) RemoveChannel(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) CanAccess(context.Context, string, string) (string, error) {
panic("not implemented")
}
func (svc *mainfluxThings) CanAccessByID(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) Identify(context.Context, string) (string, error) {
panic("not implemented")
}
func findIndex(list []string, val string) int {
for i, v := range list {
if v == val {
return i
}
}
return -1
}
+34
View File
@@ -0,0 +1,34 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package mocks
import (
"context"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/users"
"google.golang.org/grpc"
)
var _ mainflux.UsersServiceClient = (*usersServiceMock)(nil)
type usersServiceMock struct {
users map[string]string
}
// NewUsersService creates mock of users service.
func NewUsersService(users map[string]string) mainflux.UsersServiceClient {
return &usersServiceMock{users}
}
func (svc usersServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
if id, ok := svc.users[in.Value]; ok {
return &mainflux.UserID{Value: id}, nil
}
return nil, users.ErrUnauthorizedAccess
}
+714
View File
@@ -0,0 +1,714 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
)
const (
duplicateErr = "unique_violation"
uuidErr = "invalid input syntax for type uuid"
connConstraintErr = "connections_config_id_fkey"
fkViolation = "foreign_key_violation"
configFieldsNum = 8
chanFieldsNum = 3
connFieldsNum = 2
cleanupQuery = `DELETE FROM channels ch WHERE NOT EXISTS (
SELECT channel_id FROM connections c WHERE ch.mainflux_channel = c.channel_id);`
)
var _ bootstrap.ConfigRepository = (*configRepository)(nil)
type configRepository struct {
db *sqlx.DB
log logger.Logger
}
// NewConfigRepository instantiates a PostgreSQL implementation of config
// repository.
func NewConfigRepository(db *sqlx.DB, log logger.Logger) bootstrap.ConfigRepository {
return &configRepository{db: db, log: log}
}
func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (string, error) {
q := `INSERT INTO configs (mainflux_thing, owner, name, client_cert, client_key, ca_cert, mainflux_key, external_id, external_key, content, state)
VALUES (:mainflux_thing, :owner, :name, :client_cert, :client_key, :ca_cert, :mainflux_key, :external_id, :external_key, :content, :state)`
tx, err := cr.db.Beginx()
if err != nil {
return "", err
}
dbcfg := toDBConfig(cfg)
if _, err := tx.NamedExec(q, dbcfg); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
}
cr.rollback("Failed to insert a Config", tx, err)
return "", e
}
if err := insertChannels(cfg.Owner, cfg.MFChannels, tx); err != nil {
cr.rollback("Failed to insert Channels", tx, err)
return "", err
}
if err := insertConnections(cfg, connections, tx); err != nil {
cr.rollback("Failed to insert connections", tx, err)
return "", err
}
q = "DELETE FROM unknown_configs WHERE external_id = :external_id AND external_key = :external_key"
if _, err := tx.NamedExec(q, dbcfg); err != nil {
cr.rollback("Failed to remove from unknown", tx, err)
return "", err
}
if err := tx.Commit(); err != nil {
cr.rollback("Failed to commit Config save", tx, err)
}
return cfg.MFThing, nil
}
func (cr configRepository) RetrieveByID(key, id string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs
WHERE mainflux_thing = $1 AND owner = $2`
dbcfg := dbConfig{
MFThing: id,
Owner: key,
}
if err := cr.db.QueryRowx(q, id, key).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
}
return empty, err
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
}
defer rows.Close()
chans := []bootstrap.Channel{}
for rows.Next() {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
}
dbch.Owner = nullString(dbcfg.Owner)
ch, err := toChannel(dbch)
if err != nil {
return bootstrap.Config{}, err
}
chans = append(chans, ch)
}
cfg := toConfig(dbcfg)
cfg.MFChannels = chans
return cfg, nil
}
func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
search, params := cr.retrieveAll(key, filter)
n := len(params)
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs %s ORDER BY mainflux_thing LIMIT $%d OFFSET $%d`
q = fmt.Sprintf(q, search, n+1, n+2)
rows, err := cr.db.Query(q, append(params, limit, offset)...)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve configs due to %s", err))
return bootstrap.ConfigsPage{}
}
defer rows.Close()
var name, content sql.NullString
configs := []bootstrap.Config{}
for rows.Next() {
c := bootstrap.Config{Owner: key}
if err := rows.Scan(&c.MFThing, &c.MFKey, &c.ExternalID, &c.ExternalKey, &name, &content, &c.State); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
return bootstrap.ConfigsPage{}
}
c.Name = name.String
c.Content = content.String
configs = append(configs, c)
}
q = fmt.Sprintf(`SELECT COUNT(*) FROM configs %s`, search)
var total uint64
if err := cr.db.QueryRow(q, params...).Scan(&total); err != nil {
cr.log.Error(fmt.Sprintf("Failed to count configs due to %s", err))
return bootstrap.ConfigsPage{}
}
return bootstrap.ConfigsPage{
Total: total,
Limit: limit,
Offset: offset,
Configs: configs,
}
}
func (cr configRepository) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, owner, name, client_cert, client_key, ca_cert, content, state
FROM configs
WHERE external_key = $1 AND external_id = $2`
dbcfg := dbConfig{
ExternalID: externalID,
ExternalKey: externalKey,
}
if err := cr.db.QueryRowx(q, externalKey, externalID).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
}
return empty, err
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
}
defer rows.Close()
channels := []bootstrap.Channel{}
for rows.Next() {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
}
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return bootstrap.Config{}, err
}
channels = append(channels, ch)
}
cfg := toConfig(dbcfg)
cfg.MFChannels = channels
return cfg, nil
}
func (cr configRepository) Update(cfg bootstrap.Config) error {
q := `UPDATE configs SET name = $1, content = $2 WHERE mainflux_thing = $3 AND owner = $4`
content := nullString(cfg.Content)
name := nullString(cfg.Name)
res, err := cr.db.Exec(q, name, content, cfg.MFThing, cfg.Owner)
if err != nil {
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
if cnt == 0 {
return bootstrap.ErrNotFound
}
return nil
}
func (cr configRepository) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) error {
q := `UPDATE configs SET client_cert = $1, client_key = $2, ca_cert = $3 WHERE mainflux_key = $4 AND owner = $5`
res, err := cr.db.Exec(q, clientCert, clientKey, caCert, thingKey, key)
if err != nil {
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
if cnt == 0 {
return bootstrap.ErrNotFound
}
return nil
}
func (cr configRepository) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
tx, err := cr.db.Beginx()
if err != nil {
return err
}
if err := insertChannels(key, channels, tx); err != nil {
cr.rollback("Failed to insert Channels during the update", tx, err)
return err
}
if err := updateConnections(key, id, connections, tx); err != nil {
if e, ok := err.(*pq.Error); ok {
if e.Code.Name() == fkViolation && e.Constraint == connConstraintErr {
return bootstrap.ErrNotFound
}
}
cr.rollback("Failed to update connections during the update", tx, err)
return err
}
if err := tx.Commit(); err != nil {
cr.rollback("Failed to commit Config update", tx, err)
}
return nil
}
func (cr configRepository) Remove(key, id string) error {
q := `DELETE FROM configs WHERE mainflux_thing = $1 AND owner = $2`
if _, err := cr.db.Exec(q, id, key); err != nil {
return err
}
if _, err := cr.db.Exec(cleanupQuery); err != nil {
cr.log.Warn("Failed to clean dangling channels after removal")
}
return nil
}
func (cr configRepository) ChangeState(key, id string, state bootstrap.State) error {
q := `UPDATE configs SET state = $1 WHERE mainflux_thing = $2 AND owner = $3;`
res, err := cr.db.Exec(q, state, id, key)
if err != nil {
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
if cnt == 0 {
return bootstrap.ErrNotFound
}
return nil
}
func (cr configRepository) ListExisting(key string, ids []string) ([]bootstrap.Channel, error) {
var channels []bootstrap.Channel
if len(ids) == 0 {
return channels, nil
}
q := "SELECT mainflux_channel, name, metadata FROM channels WHERE owner = $1 AND mainflux_channel = ANY ($2)"
rows, err := cr.db.Queryx(q, key, pq.Array(ids))
if err != nil {
return []bootstrap.Channel{}, err
}
for rows.Next() {
var dbch dbChannel
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved channels due to %s", err))
return []bootstrap.Channel{}, err
}
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return []bootstrap.Channel{}, err
}
channels = append(channels, ch)
}
return channels, nil
}
func (cr configRepository) SaveUnknown(key, id string) error {
q := `INSERT INTO unknown_configs (external_id, external_key) VALUES ($1, $2)`
if _, err := cr.db.Exec(q, id, key); err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
return nil
}
return err
}
return nil
}
func (cr configRepository) RetrieveUnknown(offset, limit uint64) bootstrap.ConfigsPage {
q := `SELECT external_id, external_key FROM unknown_configs LIMIT $1 OFFSET $2`
rows, err := cr.db.Query(q, limit, offset)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve config due to %s", err))
return bootstrap.ConfigsPage{}
}
defer rows.Close()
items := []bootstrap.Config{}
for rows.Next() {
c := bootstrap.Config{}
if err := rows.Scan(&c.ExternalID, &c.ExternalKey); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
return bootstrap.ConfigsPage{}
}
items = append(items, c)
}
q = fmt.Sprintf(`SELECT COUNT(*) FROM unknown_configs`)
var total uint64
if err := cr.db.QueryRow(q).Scan(&total); err != nil {
cr.log.Error(fmt.Sprintf("Failed to count unknown configs due to %s", err))
return bootstrap.ConfigsPage{}
}
return bootstrap.ConfigsPage{
Total: total,
Offset: offset,
Limit: limit,
Configs: items,
}
}
func (cr configRepository) RemoveThing(id string) error {
q := `DELETE FROM configs WHERE mainflux_thing = $1`
_, err := cr.db.Exec(q, id)
if _, err := cr.db.Exec(cleanupQuery); err != nil {
cr.log.Warn("Failed to clean dangling channels after removal")
}
return err
}
func (cr configRepository) UpdateChannel(channel bootstrap.Channel) error {
dbch, err := toDBChannel("", channel)
if err != nil {
return err
}
q := `UPDATE channels SET name = :name, metadata = :metadata WHERE mainflux_channel = :mainflux_channel`
_, err = cr.db.NamedExec(q, dbch)
return err
}
func (cr configRepository) RemoveChannel(id string) error {
q := `DELETE FROM channels WHERE mainflux_channel = $1`
_, err := cr.db.Exec(q, id)
return err
}
func (cr configRepository) DisconnectThing(channelID, thingID string) error {
q := `UPDATE configs SET state = $1 WHERE EXISTS (
SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)`
_, err := cr.db.Exec(q, bootstrap.Inactive, thingID, channelID)
return err
}
func (cr configRepository) retrieveAll(key string, filter bootstrap.Filter) (string, []interface{}) {
template := `WHERE owner = $1 %s`
params := []interface{}{key}
// One empty string so that strings Join works if only one filter is applied.
queries := []string{""}
// Since key is the first param, start from 2.
counter := 2
for k, v := range filter.FullMatch {
queries = append(queries, fmt.Sprintf("%s = $%d", k, counter))
params = append(params, v)
counter++
}
for k, v := range filter.PartialMatch {
queries = append(queries, fmt.Sprintf("LOWER(%s) LIKE '%%' || $%d || '%%'", k, counter))
params = append(params, v)
counter++
}
f := strings.Join(queries, " AND ")
return fmt.Sprintf(template, f), params
}
func (cr configRepository) rollback(content string, tx *sqlx.Tx, err error) {
cr.log.Error(fmt.Sprintf("%s %s", content, err))
if err := tx.Rollback(); err != nil {
cr.log.Error(fmt.Sprintf("Failed to rollback due to %s", err))
}
}
func insertChannels(key string, channels []bootstrap.Channel, tx *sqlx.Tx) error {
if len(channels) == 0 {
return nil
}
var chans []dbChannel
for _, ch := range channels {
dbch, err := toDBChannel(key, ch)
if err != nil {
return err
}
chans = append(chans, dbch)
}
q := `INSERT INTO channels (mainflux_channel, owner, name, metadata)
VALUES (:mainflux_channel, :owner, :name, :metadata)`
if _, err := tx.NamedExec(q, chans); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
}
return e
}
return nil
}
func insertConnections(cfg bootstrap.Config, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: cfg.MFThing,
Channel: conn,
ConfigOwner: cfg.Owner,
ChannelOwner: cfg.Owner,
}
conns = append(conns, dbconn)
}
_, err := tx.NamedExec(q, conns)
return err
}
func updateConnections(key, id string, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `DELETE FROM connections
WHERE config_id = $1 AND config_owner = $2 AND channel_owner = $2
AND channel_id NOT IN ($3)`
res, err := tx.Exec(q, id, key, pq.Array(connections))
if err != nil {
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
q = `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: id,
Channel: conn,
ConfigOwner: key,
ChannelOwner: key,
}
conns = append(conns, dbconn)
}
if _, err := tx.NamedExec(q, conns); err != nil {
return err
}
if cnt == 0 {
return nil
}
_, err = tx.Exec(cleanupQuery)
return err
}
func nullString(s string) sql.NullString {
if s == "" {
return sql.NullString{}
}
return sql.NullString{
String: s,
Valid: true,
}
}
type dbConfig struct {
MFThing string `db:"mainflux_thing"`
Owner string `db:"owner"`
Name sql.NullString `db:"name"`
ClientCert sql.NullString `db:"client_cert"`
ClientKey sql.NullString `db:"client_key"`
CaCert sql.NullString `db:"ca_cert"`
MFKey string `db:"mainflux_key"`
ExternalID string `db:"external_id"`
ExternalKey string `db:"external_key"`
Content sql.NullString `db:"content"`
State bootstrap.State `db:"state"`
}
func toDBConfig(cfg bootstrap.Config) dbConfig {
return dbConfig{
MFThing: cfg.MFThing,
Owner: cfg.Owner,
Name: nullString(cfg.Name),
ClientCert: nullString(cfg.ClientCert),
ClientKey: nullString(cfg.ClientKey),
CaCert: nullString(cfg.CACert),
MFKey: cfg.MFKey,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Content: nullString(cfg.Content),
State: cfg.State,
}
}
func toConfig(dbcfg dbConfig) bootstrap.Config {
cfg := bootstrap.Config{
MFThing: dbcfg.MFThing,
Owner: dbcfg.Owner,
MFKey: dbcfg.MFKey,
ExternalID: dbcfg.ExternalID,
ExternalKey: dbcfg.ExternalKey,
State: dbcfg.State,
}
if dbcfg.Name.Valid {
cfg.Name = dbcfg.Name.String
}
if dbcfg.Content.Valid {
cfg.Content = dbcfg.Content.String
}
if dbcfg.ClientCert.Valid {
cfg.ClientCert = dbcfg.ClientCert.String
}
if dbcfg.ClientKey.Valid {
cfg.ClientKey = dbcfg.ClientKey.String
}
if dbcfg.CaCert.Valid {
cfg.CACert = dbcfg.CaCert.String
}
return cfg
}
type dbChannel struct {
ID string `db:"mainflux_channel"`
Name sql.NullString `db:"name"`
Owner sql.NullString `db:"owner"`
Metadata string `db:"metadata"`
}
func toDBChannel(owner string, ch bootstrap.Channel) (dbChannel, error) {
dbch := dbChannel{
ID: ch.ID,
Name: nullString(ch.Name),
Owner: nullString(owner),
}
metadata, err := json.Marshal(ch.Metadata)
if err != nil {
return dbChannel{}, err
}
dbch.Metadata = string(metadata)
return dbch, nil
}
func toChannel(dbch dbChannel) (bootstrap.Channel, error) {
ch := bootstrap.Channel{
ID: dbch.ID,
}
if dbch.Name.Valid {
ch.Name = dbch.Name.String
}
if err := json.Unmarshal([]byte(dbch.Metadata), &ch.Metadata); err != nil {
return bootstrap.Channel{}, err
}
return ch, nil
}
type dbConnection struct {
Config string `db:"config_id"`
Channel string `db:"channel_id"`
ConfigOwner string `db:"config_owner"`
ChannelOwner string `db:"channel_owner"`
}
+772
View File
@@ -0,0 +1,772 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
import (
"fmt"
"strconv"
"testing"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/postgres"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const numConfigs = 10
var (
config = bootstrap.Config{
MFThing: "mf-thing",
MFKey: "mf-key",
ExternalID: "external-id",
ExternalKey: "external-key",
Owner: "user@email.com",
MFChannels: []bootstrap.Channel{
bootstrap.Channel{ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}},
bootstrap.Channel{ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}},
},
Content: "content",
State: bootstrap.Inactive,
}
channels = []string{"1", "2"}
)
func TestSave(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
diff := "different"
duplicateThing := config
duplicateThing.ExternalID = diff
duplicateThing.MFKey = diff
duplicateThing.MFChannels = []bootstrap.Channel{}
duplicateExternal := config
duplicateExternal.MFThing = diff
duplicateExternal.MFKey = diff
duplicateExternal.MFChannels = []bootstrap.Channel{}
duplicateChannels := config
duplicateChannels.ExternalID = diff
duplicateChannels.MFKey = diff
duplicateChannels.MFThing = diff
cases := []struct {
desc string
config bootstrap.Config
connections []string
err error
}{
{
desc: "save a config",
config: config,
connections: channels,
err: nil,
},
{
desc: "save config with same Thing ID",
config: duplicateThing,
connections: nil,
err: bootstrap.ErrConflict,
},
{
desc: "save config with same external ID",
config: duplicateExternal,
connections: nil,
err: bootstrap.ErrConflict,
},
{
desc: "save config with same Channels",
config: duplicateChannels,
connections: channels,
err: bootstrap.ErrConflict,
},
}
for _, tc := range cases {
_, err := repo.Save(tc.config, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieveByID(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
nonexistentConfID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
cases := []struct {
desc string
owner string
id string
err error
}{
{
desc: "retrieve config",
owner: c.Owner,
id: id,
err: nil,
},
{
desc: "retrieve config with wrong owner",
owner: "2",
id: id,
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve a non-existing config",
owner: c.Owner,
id: nonexistentConfID.String(),
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve a config with invalid ID",
owner: c.Owner,
id: "invalid",
err: bootstrap.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByID(tc.owner, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieveAll(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
for i := 0; i < numConfigs; i++ {
c := config
// Use UUID to prevent conflict errors.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = uid.String()
c.Name = fmt.Sprintf("name %d", i)
c.MFThing = uid.String()
c.MFKey = uid.String()
if i%2 == 0 {
c.State = bootstrap.Active
}
if i > 0 {
c.MFChannels = nil
}
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
}
cases := []struct {
desc string
owner string
offset uint64
limit uint64
filter bootstrap.Filter
size int
}{
{
desc: "retrieve all",
owner: config.Owner,
offset: 0,
limit: uint64(numConfigs),
size: numConfigs,
},
{
desc: "retrieve subset",
owner: config.Owner,
offset: 5,
limit: uint64(numConfigs - 5),
size: numConfigs - 5,
},
{
desc: "retrieve wrong owner",
owner: "2",
offset: 0,
limit: uint64(numConfigs),
size: 0,
},
{
desc: "retrieve all active",
owner: config.Owner,
offset: 0,
limit: uint64(numConfigs),
filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}},
size: numConfigs / 2,
},
{
desc: "retrieve search by name",
owner: config.Owner,
offset: 0,
limit: uint64(numConfigs),
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}},
size: 1,
},
}
for _, tc := range cases {
ret := repo.RetrieveAll(tc.owner, tc.filter, tc.offset, tc.limit)
size := len(ret.Configs)
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size))
}
}
func TestRetrieveByExternalID(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
externalID string
externalKey string
err error
}{
{
desc: "retrieve with invalid external ID",
externalID: strconv.Itoa(numConfigs + 1),
externalKey: config.ExternalKey,
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with invalid external key",
externalID: c.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with external key",
externalID: c.ExternalID,
externalKey: c.ExternalKey,
err: nil,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByExternalID(tc.externalKey, tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdate(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.Content = "new content"
c.Name = "new name"
wrongOwner := c
wrongOwner.Owner = "3"
cases := []struct {
desc string
id string
config bootstrap.Config
err error
}{
{
desc: "update with wrong owner",
config: wrongOwner,
err: bootstrap.ErrNotFound,
},
{
desc: "update a config",
config: c,
err: nil,
},
}
for _, tc := range cases {
err := repo.Update(tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCert(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.Content = "new content"
c.Name = "new name"
wrongOwner := c
wrongOwner.Owner = "3"
cases := []struct {
desc string
key string
owner string
cert string
certKey string
ca string
err error
}{
{
desc: "update with wrong owner",
key: "",
cert: "cert",
certKey: "certKey",
ca: "",
owner: "wrong",
err: bootstrap.ErrNotFound,
},
{
desc: "update a config",
key: c.MFKey,
cert: "cert",
certKey: "certKey",
ca: "ca",
owner: c.Owner,
err: nil,
},
}
for _, tc := range cases {
err := repo.UpdateCert(tc.owner, tc.key, tc.cert, tc.key, tc.ca)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnections(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Use UUID to prevent conflicts.
uid, err = uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
c.MFChannels = []bootstrap.Channel{}
c2, err := repo.Save(c, []string{channels[0]})
require.Nil(t, err, fmt.Sprintf("Saving a config expected to succeed: %s.\n", err))
cases := []struct {
desc string
key string
id string
channels []bootstrap.Channel
connections []string
err error
}{
{
desc: "update connections of non-existing config",
key: config.Owner,
id: "unknown",
channels: nil,
connections: []string{channels[1]},
err: bootstrap.ErrNotFound,
},
{
desc: "update connections",
key: config.Owner,
id: c.MFThing,
channels: nil,
connections: []string{channels[1]},
err: nil,
},
{
desc: "update connections with existing channels",
key: config.Owner,
id: c2,
channels: nil,
connections: channels,
err: nil,
},
{
desc: "update connections no channels",
key: config.Owner,
id: c.MFThing,
channels: nil,
connections: nil,
err: nil,
},
}
for _, tc := range cases {
err := repo.UpdateConnections(tc.key, tc.id, tc.channels, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemove(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Removal works the same for both existing and non-existing
// (removed) config
for i := 0; i < 2; i++ {
err := repo.Remove(c.Owner, id)
require.Nil(t, err, fmt.Sprintf("%d: failed to remove config due to: %s", i, err))
_, err = repo.RetrieveByID(c.Owner, id)
require.Equal(t, bootstrap.ErrNotFound, err, fmt.Sprintf("%d: expected %s got %s", i, bootstrap.ErrNotFound, err))
}
}
func TestChangeState(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
owner string
id string
state bootstrap.State
err error
}{
{
desc: "change state with wrong owner",
id: saved,
owner: "2",
err: bootstrap.ErrNotFound,
},
{
desc: "change state with wrong id",
id: "wrong",
owner: c.Owner,
err: bootstrap.ErrNotFound,
},
{
desc: "change state to Active",
id: saved,
owner: c.Owner,
state: bootstrap.Active,
err: nil,
},
{
desc: "change state to Inactive",
id: saved,
owner: c.Owner,
state: bootstrap.Inactive,
err: nil,
},
}
for _, tc := range cases {
err := repo.ChangeState(tc.owner, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestListExisting(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
var chs []bootstrap.Channel
for _, ch := range config.MFChannels {
chs = append(chs, ch)
}
cases := []struct {
desc string
key string
connections []string
existing []bootstrap.Channel
}{
{
desc: "list all existing channels",
key: c.Owner,
connections: channels,
existing: chs,
},
{
desc: "list a subset of existing channels",
key: c.Owner,
connections: []string{channels[0], "5"},
existing: []bootstrap.Channel{chs[0]},
},
{
desc: "list a subset of existing channels empty",
key: c.Owner,
connections: []string{"5", "6"},
existing: []bootstrap.Channel{},
},
}
for _, tc := range cases {
existing, err := repo.ListExisting(tc.key, tc.connections)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error: %s", tc.desc, err))
assert.ElementsMatch(t, tc.existing, existing, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.existing, existing))
}
}
func TestSaveUnknown(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
cases := []struct {
desc string
externalID string
externalKey string
err error
}{
{
desc: "save unknown",
externalID: "unknown",
externalKey: "unknown",
err: nil,
},
{
desc: "save invalid unknown",
externalID: "unknown",
externalKey: "",
err: nil,
},
}
for _, tc := range cases {
err := repo.SaveUnknown(tc.externalKey, tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieveUnknown(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
for i := 0; i < numConfigs; i++ {
id, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
repo.SaveUnknown(id.String(), id.String())
}
cases := []struct {
desc string
offset uint64
limit uint64
size int
}{
{
desc: "retrieve all",
offset: 0,
limit: uint64(numConfigs),
size: numConfigs,
},
{
desc: "retrieve a subset",
offset: 5,
limit: uint64(numConfigs - 5),
size: numConfigs - 5,
},
}
for _, tc := range cases {
ret := repo.RetrieveUnknown(tc.offset, tc.limit)
size := len(ret.Configs)
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size))
}
}
func TestRemoveThing(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
for i := 0; i < 2; i++ {
err := repo.RemoveThing(saved)
assert.Nil(t, err, fmt.Sprintf("an unexpected error occured: %s\n", err))
}
}
func TestUpdateChannel(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
id := c.MFChannels[0].ID
update := bootstrap.Channel{
ID: id,
Name: "update name",
Metadata: map[string]interface{}{"update": "metadata update"},
}
err = repo.UpdateChannel(update)
assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err))
cfg, err := repo.RetrieveByID(c.Owner, c.MFThing)
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
var retreved bootstrap.Channel
for _, c := range cfg.MFChannels {
if c.ID == id {
retreved = c
break
}
}
assert.Equal(t, update, retreved, fmt.Sprintf("expected %s, go %s", update, retreved))
}
func TestRemoveChannel(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = repo.RemoveChannel(c.MFChannels[0].ID)
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
cfg, err := repo.RetrieveByID(c.Owner, c.MFThing)
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
assert.NotContains(t, cfg.MFChannels, c.MFChannels[0], fmt.Sprintf("expected to remove channel %s from %s", c.MFChannels[0], cfg.MFChannels))
}
func TestDisconnectThing(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = repo.DisconnectThing(c.MFChannels[0].ID, saved)
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
cfg, err := repo.RetrieveByID(c.Owner, c.MFThing)
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
assert.Equal(t, cfg.State, bootstrap.Inactive, fmt.Sprintf("expected ti be inactive when a connection is removed from %s", cfg))
}
func deleteChannels(repo bootstrap.ConfigRepository) error {
for _, ch := range channels {
if err := repo.RemoveChannel(ch); err != nil {
return err
}
}
return nil
}
+10
View File
@@ -0,0 +1,10 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package postgres contains repository implementations using PostgreSQL as
// the underlying database.
package postgres
+104
View File
@@ -0,0 +1,104 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
// Config defines the options that are used when connecting to a PostgreSQL instance
type Config struct {
Host string
Port string
User string
Pass string
Name string
SSLMode string
SSLCert string
SSLKey string
SSLRootCert string
}
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
if err := migrateDB(db); err != nil {
return nil, err
}
return db, nil
}
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "configs_1",
Up: []string{
`CREATE TABLE IF NOT EXISTS configs (
mainflux_thing TEXT UNIQUE NOT NULL,
owner VARCHAR(254),
name TEXT,
mainflux_key CHAR(36) UNIQUE NOT NULL,
external_id TEXT UNIQUE NOT NULL,
external_key TEXT NOT NULL,
content TEXT,
client_cert TEXT,
client_key TEXT,
ca_cert TEXT,
state BIGINT NOT NULL,
PRIMARY KEY (mainflux_thing, owner)
)`,
`CREATE TABLE IF NOT EXISTS unknown_configs (
external_id TEXT UNIQUE NOT NULL,
external_key TEXT NOT NULL,
PRIMARY KEY (external_id, external_key)
)`,
`CREATE TABLE IF NOT EXISTS channels (
mainflux_channel TEXT UNIQUE NOT NULL,
owner VARCHAR(254),
name TEXT,
metadata JSON,
PRIMARY KEY (mainflux_channel, owner)
)`,
`CREATE TABLE IF NOT EXISTS connections (
channel_id TEXT,
channel_owner VARCHAR(256),
config_id TEXT,
config_owner VARCHAR(256),
FOREIGN KEY (channel_id, channel_owner) REFERENCES channels (mainflux_channel, owner) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (config_id, config_owner) REFERENCES configs (mainflux_thing, owner) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (channel_id, channel_owner, config_id, config_owner)
)`,
},
Down: []string{
"DROP TABLE connections",
"DROP TABLE configs",
"DROP TABLE channels",
"DROP TABLE unknown_configs",
},
},
},
}
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+85
View File
@@ -0,0 +1,85 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/bootstrap/postgres"
"github.com/mainflux/mainflux/logger"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
const (
wrongID = "0"
wrongValue = "wrong-value"
)
var (
testLog, _ = logger.New(os.Stdout, logger.Info.String())
db *sqlx.DB
)
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
cfg := []string{
"POSTGRES_USER=test",
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
}
container, err := pool.Run("postgres", "10.2-alpine", cfg)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
port := container.GetPort("5432/tcp")
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err = sqlx.Open("postgres", url)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
dbConfig := postgres.Config{
Host: "localhost",
Port: port,
User: "test",
Pass: "test",
Name: "test",
SSLMode: "disable",
SSLCert: "",
SSLKey: "",
SSLRootCert: "",
}
if db, err = postgres.Connect(dbConfig); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
defer db.Close()
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"net/http"
"github.com/mainflux/mainflux"
)
// bootstrapRes represent Mainflux Response to the Bootatrap request.
// This is used as a response from ConfigReader and can easily be
// replace with any other response format.
type bootstrapRes struct {
MFThing string `json:"mainflux_id"`
MFKey string `json:"mainflux_key"`
MFChannels []channelRes `json:"mainflux_channels"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CaCert string `json:"ca_cert,omitempty"`
Content string `json:"content,omitempty"`
}
type channelRes struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
}
func (res bootstrapRes) Code() int {
return http.StatusOK
}
func (res bootstrapRes) Headers() map[string]string {
return map[string]string{}
}
func (res bootstrapRes) Empty() bool {
return false
}
type reader struct{}
// NewConfigReader return new reader which is used to generate response
// from the config.
func NewConfigReader() ConfigReader {
return reader{}
}
func (r reader) ReadConfig(cfg Config) (mainflux.Response, error) {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
}
res := bootstrapRes{
MFKey: cfg.MFKey,
MFThing: cfg.MFThing,
MFChannels: channels,
ClientCert: cfg.ClientCert,
ClientKey: cfg.ClientKey,
CaCert: cfg.CACert,
Content: cfg.Content,
}
return res, nil
}
+10
View File
@@ -0,0 +1,10 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package consumer contains events consumer for events
// published by Things service.
package consumer
+24
View File
@@ -0,0 +1,24 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package consumer
type removeEvent struct {
id string
}
type updateChannelEvent struct {
id string
name string
metadata map[string]interface{}
}
// Connection event is either connect or disconnect event.
type disconnectEvent struct {
thingID string
channelID string
}
+162
View File
@@ -0,0 +1,162 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package consumer
import (
"encoding/json"
"fmt"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
)
const (
stream = "mainflux.things"
group = "mainflux.bootstrap"
thingPrefix = "thing."
thingRemove = thingPrefix + "remove"
thingDisconnect = thingPrefix + "disconnect"
channelPrefix = "channel."
channelUpdate = channelPrefix + "update"
channelRemove = channelPrefix + "remove"
exists = "BUSYGROUP Consumer Group name already exists"
)
// EventStore represents event source for things and channels provisioning.
type EventStore interface {
// Subscribes to given subject and receives events.
Subscribe(string) error
}
type eventStore struct {
svc bootstrap.Service
client *redis.Client
consumer string
logger logger.Logger
}
// NewEventStore returns new event store instance.
func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string, log logger.Logger) EventStore {
return eventStore{
svc: svc,
client: client,
consumer: consumer,
logger: log,
}
}
func (es eventStore) Subscribe(subject string) error {
err := es.client.XGroupCreateMkStream(stream, group, "$").Err()
if err != nil && err.Error() != exists {
return err
}
for {
streams, err := es.client.XReadGroup(&redis.XReadGroupArgs{
Group: group,
Consumer: es.consumer,
Streams: []string{stream, ">"},
Count: 100,
}).Result()
if err != nil || len(streams) == 0 {
continue
}
for _, msg := range streams[0].Messages {
event := msg.Values
var err error
switch event["operation"] {
case thingRemove:
rte := decodeRemoveThing(event)
err = es.handleRemoveThing(rte)
case thingDisconnect:
dte := decodeDisconnectThing(event)
err = es.handleDisconnectThing(dte)
case channelUpdate:
uce := decodeUpdateChannel(event)
err = es.handleUpdateChannel(uce)
case channelRemove:
rce := decodeRemoveChannel(event)
err = es.handleRemoveChannel(rce)
}
if err != nil {
es.logger.Warn(fmt.Sprintf("Failed to handle event sourcing: %s", err.Error()))
break
}
es.client.XAck(stream, group, msg.ID)
}
}
}
func decodeRemoveThing(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
strmeta := read(event, "metadata", "{}")
var metadata map[string]interface{}
if err := json.Unmarshal([]byte(strmeta), metadata); err != nil {
metadata = map[string]interface{}{}
}
return updateChannelEvent{
id: read(event, "id", ""),
name: read(event, "name", ""),
metadata: metadata,
}
}
func decodeRemoveChannel(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeDisconnectThing(event map[string]interface{}) disconnectEvent {
return disconnectEvent{
channelID: read(event, "chan_id", ""),
thingID: read(event, "thing_id", ""),
}
}
func (es eventStore) handleRemoveThing(rte removeEvent) error {
return es.svc.RemoveConfigHandler(rte.id)
}
func (es eventStore) handleUpdateChannel(uce updateChannelEvent) error {
channel := bootstrap.Channel{
ID: uce.id,
Name: uce.name,
Metadata: uce.metadata,
}
return es.svc.UpdateChannelHandler(channel)
}
func (es eventStore) handleRemoveChannel(rce removeEvent) error {
return es.svc.RemoveChannelHandler(rce.id)
}
func (es eventStore) handleDisconnectThing(dte disconnectEvent) error {
return es.svc.DisconnectThingHandler(dte.channelID, dte.thingID)
}
func read(event map[string]interface{}, key, def string) string {
val, ok := event[key].(string)
if !ok {
return def
}
return val
}
+10
View File
@@ -0,0 +1,10 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package producer contains the domain events needed to support
// event sourcing of Bootstrap service actions.
package producer
+138
View File
@@ -0,0 +1,138 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package producer
import (
"strings"
"time"
"github.com/mainflux/mainflux/bootstrap"
)
const (
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingBootstrap = thingPrefix + "bootstrap"
thingStateChange = thingPrefix + "state_change"
thingUpdateConnections = thingPrefix + "update_connections"
)
type event interface {
encode() map[string]interface{}
}
var (
_ event = (*createConfigEvent)(nil)
_ event = (*updateConfigEvent)(nil)
_ event = (*removeConfigEvent)(nil)
_ event = (*bootstrapEvent)(nil)
_ event = (*changeStateEvent)(nil)
_ event = (*updateConnectionsEvent)(nil)
)
type createConfigEvent struct {
mfThing string
owner string
name string
mfChannels []string
externalID string
content string
timestamp time.Time
}
func (cce createConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cce.mfThing,
"owner": cce.owner,
"name": cce.name,
"channels": strings.Join(cce.mfChannels, ", "),
"external_id": cce.externalID,
"content": cce.content,
"timestamp": cce.timestamp.Unix(),
"operation": configCreate,
}
}
type updateConfigEvent struct {
mfThing string
name string
content string
timestamp time.Time
}
func (uce updateConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"name": uce.name,
"content": uce.content,
"timestamp": uce.timestamp.Unix(),
"operation": configUpdate,
}
}
type removeConfigEvent struct {
mfThing string
timestamp time.Time
}
func (rce removeConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": rce.mfThing,
"timestamp": rce.timestamp.Unix(),
"operation": configRemove,
}
}
type bootstrapEvent struct {
externalID string
success bool
timestamp time.Time
}
func (be bootstrapEvent) encode() map[string]interface{} {
return map[string]interface{}{
"external_id": be.externalID,
"success": be.success,
"timestamp": be.timestamp.Unix(),
"operation": thingBootstrap,
}
}
type changeStateEvent struct {
mfThing string
state bootstrap.State
timestamp time.Time
}
func (cse changeStateEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cse.mfThing,
"state": cse.state.String(),
"timestamp": cse.timestamp.Unix(),
"operation": thingStateChange,
}
}
type updateConnectionsEvent struct {
mfThing string
mfChannels []string
timestamp time.Time
}
func (uce updateConnectionsEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"channels": strings.Join(uce.mfChannels, ", "),
"timestamp": uce.timestamp.Unix(),
"operation": thingUpdateConnections,
}
}
+57
View File
@@ -0,0 +1,57 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package producer_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 redisClient *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", "5.0-alpine", nil)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
if err := pool.Retry(func() error {
redisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%s", container.GetPort("6379/tcp")),
Password: "",
DB: 0,
})
return redisClient.Ping().Err()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+182
View File
@@ -0,0 +1,182 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package producer
import (
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
)
const (
streamID = "mainflux.bootstrap"
streamLen = 1000
)
var _ bootstrap.Service = (*eventStore)(nil)
type eventStore struct {
svc bootstrap.Service
client *redis.Client
}
// NewEventStoreMiddleware returns wrapper around bootstrap service that sends
// events to event store.
func NewEventStoreMiddleware(svc bootstrap.Service, client *redis.Client) bootstrap.Service {
return eventStore{
svc: svc,
client: client,
}
}
func (es eventStore) Add(key string, cfg bootstrap.Config) (bootstrap.Config, error) {
saved, err := es.svc.Add(key, cfg)
if err != nil {
return saved, err
}
var channels []string
for _, ch := range saved.MFChannels {
channels = append(channels, ch.ID)
}
ev := createConfigEvent{
mfThing: saved.MFThing,
owner: saved.Owner,
name: saved.Name,
mfChannels: channels,
externalID: saved.ExternalID,
content: saved.Content,
timestamp: time.Now(),
}
es.add(ev)
return saved, err
}
func (es eventStore) View(key, id string) (bootstrap.Config, error) {
return es.svc.View(key, id)
}
func (es eventStore) Update(key string, cfg bootstrap.Config) error {
if err := es.svc.Update(key, cfg); err != nil {
return err
}
ev := updateConfigEvent{
mfThing: cfg.MFThing,
name: cfg.Name,
content: cfg.Content,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) error {
return es.svc.UpdateCert(key, thingKey, clientCert, clientKey, caCert)
}
func (es eventStore) UpdateConnections(key, id string, connections []string) error {
if err := es.svc.UpdateConnections(key, id, connections); err != nil {
return err
}
ev := updateConnectionsEvent{
mfThing: id,
mfChannels: connections,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) List(key string, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
return es.svc.List(key, filter, offset, limit)
}
func (es eventStore) Remove(key, id string) error {
if err := es.svc.Remove(key, id); err != nil {
return err
}
ev := removeConfigEvent{
mfThing: id,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) Bootstrap(externalKey, externalID string) (bootstrap.Config, error) {
cfg, err := es.svc.Bootstrap(externalKey, externalID)
ev := bootstrapEvent{
externalID: externalID,
timestamp: time.Now(),
success: true,
}
if err != nil {
ev.success = false
}
es.add(ev)
return cfg, err
}
func (es eventStore) ChangeState(key, id string, state bootstrap.State) error {
if err := es.svc.ChangeState(key, id, state); err != nil {
return err
}
ev := changeStateEvent{
mfThing: id,
state: state,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) RemoveConfigHandler(id string) error {
return es.svc.RemoveConfigHandler(id)
}
func (es eventStore) RemoveChannelHandler(id string) error {
return es.svc.RemoveChannelHandler(id)
}
func (es eventStore) UpdateChannelHandler(channel bootstrap.Channel) error {
return es.UpdateChannelHandler(channel)
}
func (es eventStore) DisconnectThingHandler(channelID, thingID string) error {
return es.svc.DisconnectThingHandler(channelID, thingID)
}
func (es eventStore) add(ev event) error {
record := &redis.XAddArgs{
Stream: streamID,
MaxLenApprox: streamLen,
Values: ev.encode(),
}
return es.client.XAdd(record).Err()
}
+550
View File
@@ -0,0 +1,550 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package producer_test
import (
"fmt"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
"github.com/mainflux/mainflux/bootstrap/redis/producer"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
streamID = "mainflux.bootstrap"
email = "user@example.com"
validToken = "validToken"
unknownID = "1"
unknownKey = "2"
channelsNum = 3
defaultTimout = 5
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingStateChange = thingPrefix + "state_change"
thingBootstrap = thingPrefix + "bootstrap"
thingUpdateConnections = thingPrefix + "update_connections"
)
var (
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
ExternalID: "external_id",
ExternalKey: "external_key",
MFChannels: []bootstrap.Channel{channel},
Content: "config",
}
)
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
configs := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, configs, sdk)
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func TestAdd(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
var channels []string
for _, ch := range config.MFChannels {
channels = append(channels, ch.ID)
}
invalidConfig := config
invalidConfig.MFChannels = []bootstrap.Channel{bootstrap.Channel{ID: "empty"}}
cases := []struct {
desc string
config bootstrap.Config
key string
err error
event map[string]interface{}
}{
{
desc: "create config successfully",
config: config,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": "1",
"owner": email,
"name": config.Name,
"channels": strings.Join(channels, ", "),
"external_id": config.ExternalID,
"content": config.Content,
"timestamp": time.Now().Unix(),
"operation": configCreate,
},
},
{
desc: "create invalid config",
config: invalidConfig,
key: validToken,
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Add(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestView(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
svcConfig, svcErr := svc.View(validToken, saved.MFThing)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfig, esErr := svc.View(validToken, saved.MFThing)
assert.Equal(t, svcConfig, esConfig, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfig, esConfig))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestUpdate(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
modified := saved
modified.Content = "new-config"
modified.Name = "new name"
nonExisting := config
nonExisting.MFThing = "unknown"
cases := []struct {
desc string
config bootstrap.Config
key string
err error
event map[string]interface{}
}{
{
desc: "update config successfully",
config: modified,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": modified.MFThing,
"name": modified.Name,
"content": modified.Content,
"timestamp": time.Now().Unix(),
"operation": configUpdate,
},
},
{
desc: "update non-existing config",
config: nonExisting,
key: validToken,
err: bootstrap.ErrNotFound,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Update(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestUpdateConnections(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
connections []string
err error
event map[string]interface{}
}{
{
desc: "update connections successfully",
id: saved.MFThing,
key: validToken,
connections: []string{"2"},
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"channels": "2",
"timestamp": time.Now().Unix(),
"operation": thingUpdateConnections,
},
},
{
desc: "update connections unsuccessfully",
id: saved.MFThing,
key: validToken,
connections: []string{"256"},
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.UpdateConnections(tc.key, tc.id, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestList(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
_, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
offset := uint64(0)
limit := uint64(10)
svcConfigs, svcErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfigs, esErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
assert.Equal(t, svcConfigs, esConfigs, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfigs, esConfigs))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestRemove(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
err error
event map[string]interface{}
}{
{
desc: "remove config successfully",
id: saved.MFThing,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"timestamp": time.Now().Unix(),
"operation": configRemove,
},
},
{
desc: "remove config with invalid credentials",
id: saved.MFThing,
key: "",
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Remove(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestBootstrap(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
externalID string
externalKey string
err error
event map[string]interface{}
}{
{
desc: "bootstrap successfully",
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "1",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
{
desc: "bootstrap with an error",
externalID: saved.ExternalID,
externalKey: "external",
err: bootstrap.ErrNotFound,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "0",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Bootstrap(tc.externalKey, tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestChangeState(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
state bootstrap.State
err error
event map[string]interface{}
}{
{
desc: "change state to active",
id: saved.MFThing,
key: validToken,
state: bootstrap.Active,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"state": bootstrap.Active.String(),
"timestamp": time.Now().Unix(),
"operation": thingStateChange,
},
},
{
desc: "change state invalid credentials",
id: saved.MFThing,
key: "",
state: bootstrap.Inactive,
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.ChangeState(tc.key, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func test(t *testing.T, expected, actual map[string]interface{}, description string) {
if expected != nil && actual != nil {
ts1 := expected["timestamp"].(int64)
ts2, err := strconv.ParseInt(actual["timestamp"].(string), 10, 64)
require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid timestamp, got %s", description, err))
val := ts1 == ts2 || ts2 <= ts1+defaultTimout
assert.True(t, val, fmt.Sprintf("%s: timestamp is not in valid range", description))
delete(expected, "timestamp")
delete(actual, "timestamp")
assert.Equal(t, expected, actual, fmt.Sprintf("%s: expected %v got %v\n", description, expected, actual))
}
}
+428
View File
@@ -0,0 +1,428 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"context"
"errors"
"time"
"github.com/mainflux/mainflux"
mfsdk "github.com/mainflux/mainflux/sdk/go"
)
var (
// ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity")
// ErrMalformedEntity indicates malformed entity specification.
ErrMalformedEntity = errors.New("malformed entity specification")
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
// when accessing a protected resource.
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
// ErrConflict indicates that entity with the same ID or external ID already exists.
ErrConflict = errors.New("entity already exists")
// ErrThings indicates failure to communicate with Mainflux Things service.
// It can be due to networking error or invalid/unauthorized request.
ErrThings = errors.New("error receiving response from Things service")
)
var _ Service = (*bootstrapService)(nil)
// Service specifies an API that must be fulfilled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// Add adds new Thing Config to the user identified by the provided key.
Add(string, Config) (Config, error)
// View returns Thing Config with given ID belonging to the user identified by the given key.
View(string, string) (Config, error)
// Update updates editable fields of the provided Config.
Update(string, Config) error
// UpdateCert updates an existing Config certificate and key.
// A non-nil error is returned to indicate operation failure.
UpdateCert(string, string, string, string, string) error
// UpdateConnections updates list of Channels related to given Config.
UpdateConnections(string, string, []string) error
// List returns subset of Configs with given search params that belong to the
// user identified by the given key.
List(string, Filter, uint64, uint64) (ConfigsPage, error)
// Remove removes Config with specified key that belongs to the user identified by the given key.
Remove(string, string) error
// Bootstrap returns Config to the Thing with provided external ID using external key.
Bootstrap(string, string) (Config, error)
// ChangeState changes state of the Thing with given ID and owner.
ChangeState(string, string, State) error
// Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as
// handlers for events. That's why these methods surpass ownership check.
// RemoveConfigHandler removes Configuration with id received from an event.
RemoveConfigHandler(string) error
// UpdateChannelHandler updates Channel with data received from an event.
UpdateChannelHandler(Channel) error
// RemoveChannelHandler removes Channel with id received from an event.
RemoveChannelHandler(string) error
// DisconnectHandler changes state of the Config when connect/disconnect event occurs.
DisconnectThingHandler(string, string) error
}
// ConfigReader is used to parse Config into format which will be encoded
// as a JSON and consumed from the client side. The purpose of this interface
// is to provide convenient way to generate custom configuration response
// based on the specific Config which will be consumed by the client.
type ConfigReader interface {
ReadConfig(Config) (mainflux.Response, error)
}
type bootstrapService struct {
users mainflux.UsersServiceClient
configs ConfigRepository
sdk mfsdk.SDK
}
// New returns new Bootstrap service.
func New(users mainflux.UsersServiceClient, configs ConfigRepository, sdk mfsdk.SDK) Service {
return &bootstrapService{
configs: configs,
sdk: sdk,
users: users,
}
}
func (bs bootstrapService) Add(key string, cfg Config) (Config, error) {
owner, err := bs.identify(key)
if err != nil {
return Config{}, err
}
toConnect := bs.toIDList(cfg.MFChannels)
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, toConnect)
if err != nil {
return Config{}, err
}
cfg.MFChannels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), key)
if err != nil {
return Config{}, err
}
id := cfg.MFThing
mfThing, err := bs.thing(key, id)
if err != nil {
return Config{}, err
}
cfg.MFThing = mfThing.ID
cfg.Owner = owner
cfg.State = Inactive
cfg.MFKey = mfThing.Key
saved, err := bs.configs.Save(cfg, toConnect)
if err != nil {
if id == "" {
// Fail silently.
bs.sdk.DeleteThing(cfg.MFThing, key)
}
return Config{}, err
}
cfg.MFThing = saved
cfg.MFChannels = append(cfg.MFChannels, existing...)
return cfg, nil
}
func (bs bootstrapService) View(key, id string) (Config, error) {
owner, err := bs.identify(key)
if err != nil {
return Config{}, err
}
return bs.configs.RetrieveByID(owner, id)
}
func (bs bootstrapService) Update(key string, cfg Config) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
cfg.Owner = owner
return bs.configs.Update(cfg)
}
func (bs bootstrapService) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
return bs.configs.UpdateCert(owner, thingKey, clientCert, clientKey, caCert)
}
func (bs bootstrapService) UpdateConnections(key, id string, connections []string) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return err
}
add, remove := bs.updateList(cfg, connections)
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, connections)
if err != nil {
return err
}
channels, err := bs.connectionChannels(connections, bs.toIDList(existing), key)
if err != nil {
return err
}
cfg.MFChannels = channels
var connect, disconnect []string
if cfg.State == Active {
connect = add
disconnect = remove
}
for _, c := range disconnect {
if err := bs.sdk.DisconnectThing(id, c, key); err != nil {
if err == mfsdk.ErrNotFound {
continue
}
return ErrThings
}
}
for _, c := range connect {
if err := bs.sdk.ConnectThing(id, c, key); err != nil {
if err == mfsdk.ErrNotFound {
return ErrMalformedEntity
}
return ErrThings
}
}
return bs.configs.UpdateConnections(owner, id, channels, connections)
}
func (bs bootstrapService) List(key string, filter Filter, offset, limit uint64) (ConfigsPage, error) {
owner, err := bs.identify(key)
if err != nil {
return ConfigsPage{}, err
}
if filter.Unknown {
return bs.configs.RetrieveUnknown(offset, limit), nil
}
return bs.configs.RetrieveAll(owner, filter, offset, limit), nil
}
func (bs bootstrapService) Remove(key, id string) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
return bs.configs.Remove(owner, id)
}
func (bs bootstrapService) Bootstrap(externalKey, externalID string) (Config, error) {
cfg, err := bs.configs.RetrieveByExternalID(externalKey, externalID)
if err != nil {
if err == ErrNotFound {
bs.configs.SaveUnknown(externalKey, externalID)
}
return Config{}, ErrNotFound
}
return cfg, nil
}
func (bs bootstrapService) ChangeState(key, id string, state State) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return err
}
if cfg.State == state {
return nil
}
switch state {
case Active:
for _, c := range cfg.MFChannels {
if err := bs.sdk.ConnectThing(cfg.MFThing, c.ID, key); err != nil {
return ErrThings
}
}
case Inactive:
for _, c := range cfg.MFChannels {
if err := bs.sdk.DisconnectThing(cfg.MFThing, c.ID, key); err != nil {
if err == mfsdk.ErrNotFound {
continue
}
return ErrThings
}
}
}
return bs.configs.ChangeState(owner, id, state)
}
func (bs bootstrapService) UpdateChannelHandler(channel Channel) error {
return bs.configs.UpdateChannel(channel)
}
func (bs bootstrapService) RemoveConfigHandler(id string) error {
return bs.configs.RemoveThing(id)
}
func (bs bootstrapService) RemoveChannelHandler(id string) error {
return bs.configs.RemoveChannel(id)
}
func (bs bootstrapService) DisconnectThingHandler(channelID, thingID string) error {
return bs.configs.DisconnectThing(channelID, thingID)
}
func (bs bootstrapService) identify(token string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := bs.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return "", ErrUnauthorizedAccess
}
return res.GetValue(), nil
}
// Method thing retrieves Mainflux Thing creating one if an empty ID is passed.
func (bs bootstrapService) thing(key, id string) (mfsdk.Thing, error) {
thingID := id
var err error
if id == "" {
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{}, key)
if err != nil {
return mfsdk.Thing{}, err
}
}
thing, err := bs.sdk.Thing(thingID, key)
if err != nil {
if err == mfsdk.ErrNotFound {
return mfsdk.Thing{}, ErrNotFound
}
if id != "" {
bs.sdk.DeleteThing(thingID, key)
}
return mfsdk.Thing{}, ErrThings
}
return thing, nil
}
func (bs bootstrapService) connectionChannels(channels, existing []string, key string) ([]Channel, error) {
add := make(map[string]bool, len(channels))
for _, ch := range channels {
add[ch] = true
}
for _, ch := range existing {
if add[ch] == true {
delete(add, ch)
}
}
var ret []Channel
for id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return nil, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return ret, nil
}
// Method updateList accepts config and channel IDs and returns three lists:
// 1) IDs of Channels to be added
// 2) IDs of Channels to be removed
// 3) IDs of common Channels for these two configs
func (bs bootstrapService) updateList(cfg Config, connections []string) (add, remove []string) {
var disconnect map[string]bool
disconnect = make(map[string]bool, len(cfg.MFChannels))
for _, c := range cfg.MFChannels {
disconnect[c.ID] = true
}
for _, c := range connections {
if disconnect[c] {
// Don't disconnect common elements.
delete(disconnect, c)
continue
}
// Connect new elements.
add = append(add, c)
}
for v := range disconnect {
remove = append(remove, v)
}
return
}
func (bs bootstrapService) toIDList(channels []Channel) []string {
var ret []string
for _, ch := range channels {
ret = append(ret, ch.ID)
}
return ret
}
+773
View File
@@ -0,0 +1,773 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap_test
import (
"fmt"
"net/http/httptest"
"strconv"
"testing"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
validToken = "validToken"
invalidToken = "invalidToken"
email = "test@example.com"
unknown = "unknown"
unknownID = "1"
unknownKey = "2"
channelsNum = 3
)
var (
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
ExternalID: "external_id",
ExternalKey: "external_key",
MFChannels: []bootstrap.Channel{channel},
Content: "config",
}
)
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
things := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, things, sdk)
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func TestAdd(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
neID := config
neID.MFThing = "non-existent"
wrongChannels := config
ch := channel
ch.ID = "invalid"
wrongChannels.MFChannels = append(wrongChannels.MFChannels, ch)
cases := []struct {
desc string
config bootstrap.Config
key string
err error
}{
{
desc: "add a new config",
config: config,
key: validToken,
err: nil,
},
{
desc: "add a config with an invalid ID",
config: neID,
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "add a config with wrong credentials",
config: config,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "add a config with invalid list of channels",
config: wrongChannels,
key: validToken,
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
_, err := svc.Add(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestView(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
key string
err error
}{
{
desc: "view an existing config",
id: saved.MFThing,
key: validToken,
err: nil,
},
{
desc: "view a non-existing config",
id: unknown,
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "view a config with wrong credentials",
id: config.MFThing,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
_, err := svc.View(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdate(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
modifiedCreated := saved
modifiedCreated.Content = "new-config"
modifiedCreated.Name = "new name"
nonExisting := config
nonExisting.MFThing = unknown
cases := []struct {
desc string
config bootstrap.Config
key string
err error
}{
{
desc: "update a config with state Created",
config: modifiedCreated,
key: validToken,
err: nil,
},
{
desc: "update a non-existing config",
config: nonExisting,
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "update a config with wrong credentials",
config: saved,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.Update(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCert(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
key string
thingKey string
clientCert string
clientKey string
caCert string
err error
}{
{
desc: "update certs for the valid config",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: validToken,
err: nil,
},
{
desc: "update cert for a non-existing config",
thingKey: "empty",
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "update config cert with wrong credentials",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.UpdateCert(tc.key, tc.thingKey, tc.clientCert, tc.clientKey, tc.caCert)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnections(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
created, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
externalID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = externalID.String()
active, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = svc.ChangeState(validToken, active.MFThing, bootstrap.Active)
require.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err))
nonExisting := config
nonExisting.MFThing = unknown
cases := []struct {
desc string
key string
id string
connections []string
err error
}{
{
desc: "update connections for config with state Inactive",
key: validToken,
id: created.MFThing,
connections: []string{"2"},
err: nil,
},
{
desc: "update connections for config with state Active",
key: validToken,
id: active.MFThing,
connections: []string{"3"},
err: nil,
},
{
desc: "update connections for non-existing config",
key: validToken,
id: "",
connections: []string{"3"},
err: bootstrap.ErrNotFound,
},
{
desc: "update connections with invalid channels",
key: validToken,
id: created.MFThing,
connections: []string{"wrong"},
err: bootstrap.ErrMalformedEntity,
},
{
desc: "update connections a config with wrong credentials",
key: invalidToken,
id: created.MFKey,
connections: []string{"2", "3"},
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.UpdateConnections(tc.key, tc.id, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestList(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
numThings := 101
var saved []bootstrap.Config
for i := 0; i < numThings; i++ {
c := config
id, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = id.String()
c.ExternalKey = id.String()
c.Name = fmt.Sprintf("%s-%d", config.Name, i)
s, err := svc.Add(validToken, c)
saved = append(saved, s)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
}
// Set one Thing to the different state
err := svc.ChangeState(validToken, "42", bootstrap.Active)
require.Nil(t, err, fmt.Sprintf("Changing config state expected to succeed: %s.\n", err))
saved[41].State = bootstrap.Active
unknownConfig := bootstrap.Config{
ExternalID: unknownID,
ExternalKey: unknownKey,
}
cases := []struct {
desc string
config bootstrap.ConfigsPage
filter bootstrap.Filter
offset uint64
limit uint64
key string
err error
}{
{
desc: "list configs",
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 0,
Limit: 10,
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
key: validToken,
offset: 0,
limit: 10,
err: nil,
},
{
desc: "list configs with specified name",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 0,
Limit: 100,
Configs: saved[95:96],
},
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}},
key: validToken,
offset: 0,
limit: 100,
err: nil,
},
{
desc: "list configs unauthorized",
config: bootstrap.ConfigsPage{},
filter: bootstrap.Filter{},
key: invalidToken,
offset: 0,
limit: 10,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "list last page",
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 95,
Limit: 10,
Configs: saved[95:],
},
filter: bootstrap.Filter{},
key: validToken,
offset: 95,
limit: 10,
err: nil,
},
{
desc: "list configs with Active state",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 35,
Limit: 20,
Configs: []bootstrap.Config{saved[41]},
},
filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}},
key: validToken,
offset: 35,
limit: 20,
err: nil,
},
{
desc: "list unknown configs",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 0,
Limit: 20,
Configs: []bootstrap.Config{unknownConfig},
},
filter: bootstrap.Filter{Unknown: true},
key: validToken,
offset: 0,
limit: 20,
err: nil,
},
}
for _, tc := range cases {
result, err := svc.List(tc.key, tc.filter, tc.offset, tc.limit)
assert.ElementsMatch(t, tc.config.Configs, result.Configs, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Configs, result.Configs))
assert.Equal(t, tc.config.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Total, result.Total))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemove(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
key string
err error
}{
{
desc: "view a config with wrong credentials",
id: saved.MFThing,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "remove an existing config",
id: saved.MFThing,
key: validToken,
err: nil,
},
{
desc: "remove removed config",
id: saved.MFThing,
key: validToken,
err: nil,
},
{
desc: "remove non-existing config",
id: unknown,
key: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.Remove(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestBootstrap(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
config bootstrap.Config
externalKey string
externalID string
err error
}{
{
desc: "bootstrap using invalid external id",
config: bootstrap.Config{},
externalID: "invalid",
externalKey: saved.ExternalKey,
err: bootstrap.ErrNotFound,
},
{
desc: "bootstrap using invalid external key",
config: bootstrap.Config{},
externalID: saved.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
},
{
desc: "bootstrap an existing config",
config: saved,
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
},
}
for _, tc := range cases {
config, err := svc.Bootstrap(tc.externalKey, tc.externalID)
assert.Equal(t, tc.config, config, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.config, config))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestChangeState(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
state bootstrap.State
id string
key string
err error
}{
{
desc: "change state with wrong credentials",
state: bootstrap.Active,
id: saved.MFThing,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "change state of non-existing config",
state: bootstrap.Active,
id: unknown,
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "change state to Active",
state: bootstrap.Active,
id: saved.MFThing,
key: validToken,
err: nil,
},
{
desc: "change state to current state",
state: bootstrap.Active,
id: saved.MFThing,
key: validToken,
err: nil,
},
{
desc: "change state to Inactive",
state: bootstrap.Inactive,
id: saved.MFThing,
key: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.ChangeState(tc.key, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateChannelHandler(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
_, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
ch := bootstrap.Channel{
ID: channel.ID,
Name: "new name",
Metadata: map[string]interface{}{"meta": "new"},
}
cases := []struct {
desc string
channel bootstrap.Channel
err error
}{
{
desc: "update an existing channel",
channel: ch,
err: nil,
},
{
desc: "update a non-existing channel",
channel: bootstrap.Channel{ID: ""},
err: nil,
},
}
for _, tc := range cases {
err := svc.UpdateChannelHandler(tc.channel)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemoveChannelHandler(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
_, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
err error
}{
{
desc: "remove an existing channel",
id: channel.ID,
err: nil,
},
{
desc: "remove a non-existing channel",
id: "unknown",
err: nil,
},
}
for _, tc := range cases {
err := svc.RemoveChannelHandler(tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemoveCoinfigHandler(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
err error
}{
{
desc: "remove an existing conifg",
id: saved.MFThing,
err: nil,
},
{
desc: "remove a non-existing channel",
id: "unknown",
err: nil,
},
}
for _, tc := range cases {
err := svc.RemoveConfigHandler(tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestDisconnectThingsHandler(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
thingID string
channelID string
err error
}{
{
desc: "disconnect",
channelID: channel.ID,
thingID: saved.MFThing,
err: nil,
},
{
desc: "disconnect dicsonnected",
channelID: channel.ID,
thingID: saved.MFThing,
err: nil,
},
}
for _, tc := range cases {
err := svc.DisconnectThingHandler(tc.channelID, tc.thingID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+30
View File
@@ -0,0 +1,30 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import "strconv"
const (
// Inactive Thing is created, but not able to exchange messages using Mainflux.
Inactive State = iota
// Active Thing is created, configured, and whitelisted.
Active
)
// State represents corresponding Mainflux Thing state. The possible Config States
// as well as description of what that State represents are given in the table:
// | State | What it means |
// |----------+--------------------------------------------------------------------------------|
// | Inactive | Thing is created, but isn't able to communicate over Mainflux |
// | Active | Thing is able to communicate using Mainflux |
type State int
// String returns string representation of State.
func (s State) String() string {
return strconv.Itoa(int(s))
}
+483
View File
@@ -0,0 +1,483 @@
swagger: "2.0"
info:
title: Mainflux Bootstrap service
description: HTTP API for managing platform things configuration.
version: "1.0.0"
consumes:
- "application/json"
produces:
- "application/json"
paths:
/things/configs:
post:
summary: Adds new config
description: |
Adds new config to the list of config owned by user identified using
the provided access token.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- name: config
description: JSON-formatted document describing the new config.
in: body
schema:
$ref: "#/definitions/ConfigReq"
required: true
responses:
201:
description: Config registered.
headers:
Location:
type: string
description: Created config's relative URL (i.e. /things/configs/{configId}).
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
get:
summary: Retrieves managed configs
description: |
Retrieves a list of managed configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
dataset is consumed either by making subsequent requests, or by
increasing the subset size of the initial request.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/Limit"
- $ref: "#/parameters/Offset"
- $ref: "#/parameters/State"
- $ref: "#/parameters/Name"
responses:
200:
description: |
Data retrieved. Configs from this list don't contain channels.
schema:
$ref: "#/definitions/ConfigList"
400:
description: Failed due to malformed query parameters.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/bootstrap/{externalId}:
get:
summary: Retrieves configuration
description: |
Retrieves a configuration with given external ID and external key.
tags:
- configs
parameters:
- $ref: "#/parameters/ConfigAuthorization"
- $ref: "#/parameters/ExternalId"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/BootstrapRes"
404:
description: |
Failed to retrieve corresponding config. Thing which attempted
to bootstrap is saved as an unknown Thing and can be listed and
added to the service later.
500:
$ref: "#/responses/ServiceError"
/things/configs/{configId}:
get:
summary: Retrieves config info (with channels)
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/ConfigRes"
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
500:
$ref: "#/responses/ServiceError"
put:
summary: Updates config info
description: |
Update is performed by replacing the current resource data with values
provided in a request payload. Note that the owner, ID, external ID,
external key, Mainflux Thing ID and key cannot be changed.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: config
description: JSON-formatted document describing the updated thing.
in: body
schema:
$ref: "#/definitions/ConfigUpdateReq"
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
delete:
summary: Removes a Config
description: |
Removes a Config. In case of successfull removal the service will ensure
that the removed config is disconnected from all of the Maifnlux channels.
tags:
- confgis
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
responses:
204:
description: Config removed.
400:
description: Failed due to malformed config's ID.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/configs/certs/{thingKey}:
put:
summary: Updates certs
description: |
Update is performed by replacing the current certificate data with values
provided in a request payload.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ThingKey"
- name: config
description: JSON-formatted document describing the updated thing.
in: body
schema:
$ref: "#/definitions/ConfigUpdateCertReq"
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
/things/configs/connections/{configId}:
put:
summary: Updates channels the thing is connected to
description: |
Update connections performs update of the channel list corresponding
Thing is connected to.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: channels
description: Array if IDs the thing is be connected to.
in: body
schema:
$ref: "#/definitions/ConfigUpdateConnReq"
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
/things/state/{configId}:
put:
summary: Updates Config state.
description: |
Updating state represents enabling/disabling Config, i.e. connecting
and disconnecting corresponding Mainflux Thing to the list of Channels.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: state
description: New state of the Config.
in: body
schema:
type: object
properties:
state:
type: integer
enum:
- inactive
- active
responses:
204:
description: Config removed.
400:
description: Failed due to malformed config's ID.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/unknown/configs:
get:
summary: Get a list of unsucessfully bootstrapped Things
description: |
Retrieves a list of unknown configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
dataset is consumed either by making subsequent requests, or by
increasing the subset size of the initial request.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/Limit"
- $ref: "#/parameters/Offset"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/ConfigList"
400:
description: Failed due to malformed query parameters.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
parameters:
Authorization:
name: Authorization
description: User's access token.
in: header
type: string
required: true
ConfigAuthorization:
name: configAuthorization
description: Configuration external key.
in: header
type: string
required: true
ConfigId:
name: configId
description: Unique Config identifier. It's the ID of the corresponding Thing.
in: path
type: string
required: true
ThingKey:
name: thingKey
description: Unique Thing key.
in: path
type: string
required: true
ExternalId:
name: externalId
description: Unique Config identifier provided by external entity.
in: path
type: string
required: true
Limit:
name: limit
description: Size of the subset to retrieve.
in: query
type: integer
default: 10
maximum: 100
minimum: 1
required: false
Offset:
name: offset
description: Number of items to skip during retrieval.
in: query
type: integer
default: 0
minimum: 0
required: false
State:
name: state
description: A state of items
in: query
type: integer
enum:
- inactive
- active
required: false
Name:
name: name
description: Name of the config. Search by name is partial-match and case-insensitive.
in: query
type: string
required: false
responses:
ServiceError:
description: Unexpected server-side error occured.
definitions:
ConfigList:
type: object
properties:
total:
type: integer
description: Total number of results.
minimum: 0
offset:
type: integer
description: Number of items to skip during retrieval.
minimum: 0
default: 0
limit:
type: integer
description: Size of the subset to retrieve.
maximum: 100
default: 10
configs:
type: array
minItems: 0
uniqueItems: true
items:
$ref: "#/definitions/ConfigRes"
required:
- confgis
State:
type: integer
enum:
- active
- inactive
ConfigRes:
type: object
properties:
mainflux_id:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: object
properties:
id:
type: string
description: ID of the Channel.
name:
type: string
description: Name of the Channel.
metadata:
type: object
description: Custom metadata related to the Channel.
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
external_key:
type: string
description: External key.
content:
type: string
description: Free-form custom configuration.
state:
$ref: '#/definitions/State'
required:
- external_id
- external_key
BootstrapRes:
type: object
properties:
mainflux_id:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: string
content:
type: string
description: Free-form custom configuration.
required:
- mainflux_id
- mainflux_key
- mainflux_channels
- content
ConfigReq:
type: object
properties:
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
external_key:
type: string
description: External key.
thing_id:
type: string
description: ID of the corresponding Mainflux Thing.
channels:
type: array
minItems: 0
items:
type: string
content:
type: string
required:
- external_id
- external_key
ConfigUpdateReq:
type: object
properties:
content:
type: string
name:
type: string
required:
- content
- name
ConfigUpdateConnReq:
type: object
properties:
channels:
type: array
minItems: 0
items:
type: string
ConfigUpdateCertReq:
type: object
properties:
client_cert:
type: string
client_key:
type: string
ca_cert:
type: string
+43 -22
View File
@@ -7,12 +7,12 @@ make cli
## Usage
### Service
#### Get the service verison
#### Get the version of Mainflux services
```
mainflux-cli version
```
### User management
### Users management
#### Create User
```
mainflux-cli users create john.doe@email.com password
@@ -24,24 +24,19 @@ mainflux-cli users token john.doe@email.com password
```
### System Provisioning
#### Provision Device
#### Create Thing (type Device)
```
mainflux-cli things create '{"type":"device", "name":"nyDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Provision Application
#### Create Thing (type Application)
```
mainflux-cli things create '{"type":"app", "name":"nyDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Retrieve All Things
#### Update Thing
```
mainflux-cli things get all --offset=1 --limit=5 <user_auth_token>
```
#### Retrieve Thing By ID
```
mainflux-cli things get <thing_id> --offset=1 --limit=5 <user_auth_token>
mainflux-cli things update '{"id":"<thing_id>", "name":"myNewName"}' <user_auth_token>
```
#### Remove Thing
@@ -49,39 +44,65 @@ mainflux-cli things get <thing_id> --offset=1 --limit=5 <user_auth_token>
mainflux-cli things delete <thing_id> <user_auth_token>
```
#### Provision Channel
#### Retrieve a subset list of provisioned Things
```
mainflux-cli channels create '{"name":"nyChannel"}' <user_auth_token>
mainflux-cli things get all --offset=1 --limit=5 <user_auth_token>
```
#### Retrieve All Channels
#### Retrieve Thing By ID
```
mainflux-cli channels get all --offset=1 --limit=5 <user_auth_token>
mainflux-cli things get <thing_id> <user_auth_token>
```
#### Retrievie Channel By ID
#### Create Channel
```
mainflux-cli channels get <channel_id> --offset=1 --limit=5 <user_auth_token>
mainflux-cli channels create '{"name":"myChannel"}' <user_auth_token>
```
#### Update Channel
```
mainflux-cli channels update '{"id":"<channel_id>","name":"myNewName"}' <user_auth_token>
```
#### Remove Channel
```
mainflux-cli channels delete <channel_id> <user_auth_token>
```
#### Retrieve a subset list of provisioned Channels
```
mainflux-cli channels get all --offset=1 --limit=5 <user_auth_token>
```
#### Retrieve Channel By ID
```
mainflux-cli channels get <channel_id> <user_auth_token>
```
### Access control
#### Connect Thing to a Channel
#### Connect Thing to Channel
```
mainflux-cli things connect <thing_id> <channel_id> <user_auth_token>
```
#### Disconnect Things from a Channel
#### Disconnect Thing from Channel
```
mainflux-cli things disconnect <thing_id> <channel_id> <user_auth_token>
```
#### Retrieve a subset list of Channels connected to Thing
```
mainflux-cli things connections <thing_id> <user_auth_token>
```
#### Retrieve a subset list of Things connected to Channel
```
mainflux-cli channels connections <channel_id> <user_auth_token>
```
### Messaging
#### Send a message over HTTP
```
mainflux-cli msg send <channel_id> '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' <thing_auth_token>
mainflux-cli messages send <channel_id> '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' <thing_auth_token>
```
+83 -58
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,38 +21,77 @@ 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
}
logCreated(id)
},
},
cobra.Command{
Use: "get",
Short: "get all/<channel_id> <user_auth_token>",
Short: "get [all | <channel_id>] <user_auth_token>",
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), Name)
if err != nil {
logError(err)
return
}
logJSON(l)
return
}
GetChannel(args[0], args[1])
c, err := sdk.Channel(args[0], args[1])
if err != nil {
logError(err)
return
}
logJSON(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)
if len(args) != 2 {
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,53 @@ 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()
},
},
cobra.Command{
Use: "connections",
Short: "connections <channel_id> <user_auth_token>",
Long: `List of Things connected to Channel`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
return
}
cl, err := sdk.ThingsByChannel(args[1], args[0], uint64(Offset), uint64(Limit))
if err != nil {
logError(err)
return
}
logJSON(cl)
},
},
}
// 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`,
Short: "Channels management",
Long: `Channels management: create, get, update or delete Channel and get list of Things connected to Channel`,
Run: func(cmd *cobra.Command, args []string) {
LogUsage(cmd.Short)
logUsage("channels [create | get | update | delete | connections]")
},
}
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}
}
+37 -26
View File
@@ -7,53 +7,64 @@
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>[.<subtopic>...] <JSON_string> <thing_key>",
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()
},
},
cobra.Command{
Use: "read",
Short: "read <channel_id>[.<subtopic>...] <thing_key>",
Long: `Reads all channel messages`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
return
}
m, err := sdk.ReadMessages(args[0], args[1])
if err != nil {
logError(err)
return
}
logJSON(m)
},
},
}
// NewMessagesCmd returns messages command.
func NewMessagesCmd() *cobra.Command {
cmd := cobra.Command{
Use: "msg",
Short: "Send or retrieve messages",
Long: `Send or retrieve messages: control message flow on the channel`,
Use: "messages",
Short: "Send or read messages",
Long: `Send or read messages using the http-adapter and the configured database reader`,
Run: func(cmd *cobra.Command, args []string) {
logUsage("messages [send | read]")
},
}
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)
}
+246
View File
@@ -0,0 +1,246 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package cli
import (
"bufio"
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"github.com/docker/docker/pkg/namesgenerator"
mfxsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/spf13/cobra"
)
var errMalformedCSV = errors.New("malformed CSV")
func createThing(name, token string) (mfxsdk.Thing, error) {
id, err := sdk.CreateThing(mfxsdk.Thing{Name: name}, token)
if err != nil {
return mfxsdk.Thing{}, err
}
t, err := sdk.Thing(id, token)
if err != nil {
return mfxsdk.Thing{}, err
}
m := mfxsdk.Thing{
ID: id,
Name: name,
Key: t.Key,
}
return m, nil
}
func createChannel(name, token string) (mfxsdk.Channel, error) {
id, err := sdk.CreateChannel(mfxsdk.Channel{Name: name}, token)
if err != nil {
return mfxsdk.Channel{}, nil
}
c := mfxsdk.Channel{
ID: id,
Name: name,
}
return c, nil
}
var cmdProvision = []cobra.Command{
cobra.Command{
Use: "things",
Short: "things <things_csv> <user_token>",
Long: `Provisions things`,
Run: func(cmd *cobra.Command, args []string) {
things := []mfxsdk.Thing{}
if len(args) != 2 {
logUsage(cmd.Short)
return
}
c, err := os.Open(args[0])
if err != nil {
logError(err)
return
}
reader := csv.NewReader(bufio.NewReader(c))
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
logError(err)
return
}
if len(l) < 1 {
logError(errMalformedCSV)
return
}
m, err := createThing(l[0], args[1])
if err != nil {
logError(err)
return
}
things = append(things, m)
}
logJSON(things)
},
},
cobra.Command{
Use: "channels",
Short: "channels <channels_csv> <user_token>",
Long: `Provisions channels`,
Run: func(cmd *cobra.Command, args []string) {
channels := []mfxsdk.Channel{}
if len(args) != 2 {
logUsage(cmd.Short)
return
}
c, err := os.Open(args[0])
if err != nil {
logError(err)
return
}
reader := csv.NewReader(bufio.NewReader(c))
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
logError(err)
return
}
if len(l) < 1 {
logError(errMalformedCSV)
return
}
c, err := createChannel(l[0], args[1])
if err != nil {
logError(err)
return
}
channels = append(channels, c)
}
logJSON(channels)
},
},
cobra.Command{
Use: "test",
Short: "test",
Long: `Provisions test setup: one test user, two things and two channels. \
Connect both things to one of the channels, \
and only on thing to other channel.`,
Run: func(cmd *cobra.Command, args []string) {
numThings := 2
numChan := 2
things := []mfxsdk.Thing{}
channels := []mfxsdk.Channel{}
if len(args) != 0 {
logUsage(cmd.Short)
return
}
un := fmt.Sprintf("%s@email.com", namesgenerator.GetRandomName(0))
// Create test user
user := mfxsdk.User{
Email: un,
Password: "123",
}
if err := sdk.CreateUser(user); err != nil {
logError(err)
return
}
ut, err := sdk.CreateToken(user)
if err != nil {
logError(err)
return
}
// Create things
for i := 0; i < numThings; i++ {
n := fmt.Sprintf("d%d", i)
m, err := createThing(n, ut)
if err != nil {
logError(err)
return
}
things = append(things, m)
}
// Create channels
for i := 0; i < numChan; i++ {
n := fmt.Sprintf("c%d", i)
c, err := createChannel(n, ut)
if err != nil {
logError(err)
return
}
channels = append(channels, c)
}
// Connect things to channels - first thing to both channels, second only to first
for i := 0; i < numThings; i++ {
if err := sdk.ConnectThing(things[i].ID, channels[i].ID, ut); err != nil {
logError(err)
return
}
if i%2 == 0 {
if i+1 >= len(channels) {
break
}
if err := sdk.ConnectThing(things[i].ID, channels[i+1].ID, ut); err != nil {
logError(err)
return
}
}
}
logJSON(user, ut, things, channels)
},
},
}
// NewProvisionCmd returns provision command.
func NewProvisionCmd() *cobra.Command {
cmd := cobra.Command{
Use: "provision",
Short: "Provision things and channels from config file",
Long: `Provision things and channels: use csv config file to provision things and channels`,
}
for i := range cmdProvision {
cmd.AddCommand(&cmdProvision[i])
}
return &cmd
}
+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
}
+99 -77
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
}
logCreated(id)
},
},
cobra.Command{
Use: "get",
Short: "get all/<thing_id> <user_auth_token>",
Long: `Get all thingss or thing by id`,
Short: "get [all | <thing_id>] <user_auth_token>",
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), Name)
if err != nil {
logError(err)
return
}
logJSON(l)
return
}
GetThing(args[0], args[1])
t, err := sdk.Thing(args[0], args[1])
if err != nil {
logError(err)
return
}
logJSON(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)
if len(args) != 2 {
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,53 @@ 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()
},
},
cobra.Command{
Use: "connections",
Short: "connections <thing_id> <user_auth_token>",
Long: `List of Channels connected to Thing`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
return
}
cl, err := sdk.ChannelsByThing(args[1], args[0], uint64(Offset), uint64(Limit))
if err != nil {
logError(err)
return
}
logJSON(cl)
},
},
}
// NewThingsCmd returns things command.
func NewThingsCmd() *cobra.Command {
cmd := cobra.Command{
Use: "things",
Short: "things <options>",
Long: `Things handling: create, delete or update things.`,
Short: "Things management",
Long: `Things management: create, get, update or delete Thing, connect or disconnect Thing from Channel and get the list of Channels connected to Thing`,
Run: func(cmd *cobra.Command, args []string) {
LogUsage(cmd.Short)
logUsage("things [create | get | update | delete | connect | disconnect | connections]")
},
}
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)
}
+31 -27
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
}
logCreated(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)`,
Short: "Users management",
Long: `Users management: create accounts and tokens"`,
Run: func(cmd *cobra.Command, args []string) {
LogUsage(cmd.Short)
logUsage("Usage: users [create | token]")
},
}
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)
}
+34 -50
View File
@@ -8,68 +8,52 @@
package cli
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/fatih/color"
"github.com/hokaccha/go-prettyjson"
prettyjson "github.com/hokaccha/go-prettyjson"
)
const contentType = "application/json"
var (
// Limit query parameter
Limit uint = 10
// Offset query parameter
Offset uint = 0
// Name query parameter
Name string = ""
)
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)
}
// 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))
func logJSON(iList ...interface{}) {
for _, i := range iList {
m, err := json.Marshal(i)
if err != nil {
fmt.Printf("%s\n\n", color.BlueString(string(body)))
logError(err)
return
}
fmt.Printf("%s\n\n", string(pj))
pj, err := prettyjson.Format(m)
if err != nil {
logError(err)
return
}
fmt.Printf("\n%s\n\n", string(pj))
}
}
func LogUsage(u string) {
fmt.Printf(color.YellowString("Usage: %s\n\n"), u)
func logUsage(u string) {
fmt.Printf(color.YellowString("\nusage: %s\n\n"), u)
}
func LogError(err error) {
fmt.Printf("%s\n\n", color.RedString(err.Error()))
func logError(err error) {
fmt.Printf("\n%s\n\n", color.RedString(err.Error()))
}
func logOK() {
fmt.Printf("\n%s\n\n", color.BlueString("ok"))
}
func logCreated(e string) {
fmt.Printf(color.BlueString("\ncreated: %s\n\n"), e)
}
+11 -14
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.`,
Short: "Mainflux services version",
Long: `Mainflux services version: get version of Mainflux Things Service`,
Run: func(cmd *cobra.Command, args []string) {
Version()
v, err := sdk.Version()
if err != nil {
logError(err)
return
}
logJSON(v)
},
}
}
// Version - server health check
func Version() {
url := fmt.Sprintf("%s/version", serverAddr)
FormatResLog(httpClient.Get(url))
}
+323
View File
@@ -0,0 +1,323 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
rediscons "github.com/mainflux/mainflux/bootstrap/redis/consumer"
redisprod "github.com/mainflux/mainflux/bootstrap/redis/producer"
"github.com/mainflux/mainflux/logger"
opentracing "github.com/opentracing/opentracing-go"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
r "github.com/go-redis/redis"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
api "github.com/mainflux/mainflux/bootstrap/api"
"github.com/mainflux/mainflux/bootstrap/postgres"
mflog "github.com/mainflux/mainflux/logger"
mfsdk "github.com/mainflux/mainflux/sdk/go"
usersapi "github.com/mainflux/mainflux/users/api/grpc"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "bootstrap"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defServerCert = ""
defServerKey = ""
defBaseURL = "http://localhost"
defThingsPrefix = ""
defUsersURL = "localhost:8181"
defThingsESURL = "localhost:6379"
defThingsESPass = ""
defThingsESDB = "0"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defInstanceName = "bootstrap"
defJaegerURL = "localhost:6831"
defUsersTimeout = "1" // in seconds
envLogLevel = "MF_BOOTSTRAP_LOG_LEVEL"
envDBHost = "MF_BOOTSTRAP_DB_HOST"
envDBPort = "MF_BOOTSTRAP_DB_PORT"
envDBUser = "MF_BOOTSTRAP_DB_USER"
envDBPass = "MF_BOOTSTRAP_DB_PASS"
envDBName = "MF_BOOTSTRAP_DB"
envDBSSLMode = "MF_BOOTSTRAP_DB_SSL_MODE"
envDBSSLCert = "MF_BOOTSTRAP_DB_SSL_CERT"
envDBSSLKey = "MF_BOOTSTRAP_DB_SSL_KEY"
envDBSSLRootCert = "MF_BOOTSTRAP_DB_SSL_ROOT_CERT"
envClientTLS = "MF_BOOTSTRAP_CLIENT_TLS"
envCACerts = "MF_BOOTSTRAP_CA_CERTS"
envPort = "MF_BOOTSTRAP_PORT"
envServerCert = "MF_BOOTSTRAP_SERVER_CERT"
envServerKey = "MF_BOOTSTRAP_SERVER_KEY"
envBaseURL = "MF_SDK_BASE_URL"
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
envUsersURL = "MF_USERS_URL"
envThingsESURL = "MF_THINGS_ES_URL"
envThingsESPass = "MF_THINGS_ES_PASS"
envThingsESDB = "MF_THINGS_ES_DB"
envESURL = "MF_BOOTSTRAP_ES_URL"
envESPass = "MF_BOOTSTRAP_ES_PASS"
envESDB = "MF_BOOTSTRAP_ES_DB"
envInstanceName = "MF_BOOTSTRAP_INSTANCE_NAME"
envJaegerURL = "MF_JAEGER_URL"
envUsersTimeout = "MF_BOOTSTRAP_USERS_TIMEOUT"
)
type config struct {
logLevel string
dbConfig postgres.Config
clientTLS bool
caCerts string
httpPort string
serverCert string
serverKey string
baseURL string
thingsPrefix string
usersURL string
esThingsURL string
esThingsPass string
esThingsDB string
esURL string
esPass string
esDB string
instanceName string
jaegerURL string
usersTimeout time.Duration
}
func main() {
cfg := loadConfig()
logger, err := mflog.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
conn := connectToUsers(cfg, logger)
defer conn.Close()
thingsESConn := connectToRedis(cfg.esThingsURL, cfg.esThingsPass, cfg.esThingsDB, logger)
defer thingsESConn.Close()
esClient := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
defer esClient.Close()
usersTracer, usersCloser := initJaeger("users", cfg.jaegerURL, logger)
defer usersCloser.Close()
svc := newService(conn, usersTracer, db, logger, esClient, cfg)
errs := make(chan error, 2)
go startHTTPServer(svc, cfg, logger, errs)
go subscribeToThingsES(svc, thingsESConn, cfg.instanceName, logger)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("Bootstrap service terminated: %s", err))
}
func loadConfig() config {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
tls = false
}
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
timeout, err := strconv.ParseInt(mainflux.Env(envUsersTimeout, defUsersTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envUsersTimeout, err.Error())
}
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
httpPort: mainflux.Env(envPort, defPort),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
baseURL: mainflux.Env(envBaseURL, defBaseURL),
thingsPrefix: mainflux.Env(envThingsPrefix, defThingsPrefix),
usersURL: mainflux.Env(envUsersURL, defUsersURL),
esThingsURL: mainflux.Env(envThingsESURL, defThingsESURL),
esThingsPass: mainflux.Env(envThingsESPass, defThingsESPass),
esThingsDB: mainflux.Env(envThingsESDB, defThingsESDB),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
instanceName: mainflux.Env(envInstanceName, defInstanceName),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
usersTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToDB(cfg postgres.Config, logger mflog.Logger) *sqlx.DB {
db, err := postgres.Connect(cfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
os.Exit(1)
}
return db
}
func connectToRedis(redisURL, redisPass, redisDB string, logger mflog.Logger) *r.Client {
db, err := strconv.Atoi(redisDB)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to redis: %s", err))
os.Exit(1)
}
return r.NewClient(&r.Options{
Addr: redisURL,
Password: redisPass,
DB: db,
})
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(conn *grpc.ClientConn, usersTracer opentracing.Tracer, db *sqlx.DB, logger mflog.Logger, esClient *r.Client, cfg config) bootstrap.Service {
thingsRepo := postgres.NewConfigRepository(db, logger)
config := mfsdk.Config{
BaseURL: cfg.baseURL,
ThingsPrefix: cfg.thingsPrefix,
}
sdk := mfsdk.NewSDK(config)
users := usersapi.NewClient(usersTracer, conn, cfg.usersTimeout)
svc := bootstrap.New(users, thingsRepo, sdk)
svc = redisprod.NewEventStoreMiddleware(svc, esClient)
svc = api.NewLoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "bootstrap",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "bootstrap",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
func connectToUsers(cfg config, logger mflog.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to create tls credentials: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
opts = append(opts, grpc.WithInsecure())
logger.Info("gRPC communication is not encrypted")
}
conn, err := grpc.Dial(cfg.usersURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
os.Exit(1)
}
return conn
}
func startHTTPServer(svc bootstrap.Service, cfg config, logger mflog.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.httpPort)
if cfg.serverCert != "" || cfg.serverKey != "" {
logger.Info(fmt.Sprintf("Bootstrap service started using https on port %s with cert %s key %s",
cfg.httpPort, cfg.serverCert, cfg.serverKey))
errs <- http.ListenAndServeTLS(p, cfg.serverCert, cfg.serverKey, api.MakeHandler(svc, bootstrap.NewConfigReader()))
return
}
logger.Info(fmt.Sprintf("Bootstrap service started using http on port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, bootstrap.NewConfigReader()))
}
func subscribeToThingsES(svc bootstrap.Service, client *r.Client, consumer string, logger mflog.Logger) {
eventStore := rediscons.NewEventStore(svc, client, consumer, logger)
logger.Info("Subscribed to Redis Event Store")
if err := eventStore.Subscribe("mainflux.things"); err != nil {
logger.Warn(fmt.Sprintf("Botstrap service failed to subscribe to event sourcing: %s", err))
}
}
+124 -30
View File
@@ -9,57 +9,90 @@ package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
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"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
sep = ","
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defThingsURL = "localhost:8181"
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defDBUsername = ""
defDBPassword = ""
defDBPort = "9042"
defThingsURL = "localhost:8181"
defClientTLS = "false"
defCACerts = ""
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envPort = "MF_CASSANDRA_READER_PORT"
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_CASSANDRA_READER_LOG_LEVEL"
envPort = "MF_CASSANDRA_READER_PORT"
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
envDBUsername = "MF_CASSANDRA_READER_DB_USERNAME"
envDBPassword = "MF_CASSANDRA_READER_DB_PASSWORD"
envDBPort = "MF_CASSANDRA_READER_DB_PORT"
envThingsURL = "MF_THINGS_URL"
envClientTLS = "MF_CASSANDRA_READER_CLIENT_TLS"
envCACerts = "MF_CASSANDRA_READER_CA_CERTS"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_CASSANDRA_READER_THINGS_TIMEOUT"
)
type config struct {
port string
cluster string
keyspace string
thingsURL string
logLevel string
port string
dbCfg cassandra.DBConfig
thingsURL string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
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)
session := connectToCassandra(cfg.dbCfg, logger)
defer session.Close()
conn := connectToThings(cfg.thingsURL, logger)
conn := connectToThings(cfg, logger)
defer conn.Close()
tc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
repo := newService(session, logger)
errs := make(chan error, 2)
@@ -72,21 +105,48 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
err := <-errs
err = <-errs
logger.Error(fmt.Sprintf("Cassandra reader service terminated: %s", err))
}
func loadConfig() config {
dbPort, err := strconv.Atoi(mainflux.Env(envDBPort, defDBPort))
if err != nil {
log.Fatal(err)
}
dbCfg := cassandra.DBConfig{
Hosts: strings.Split(mainflux.Env(envCluster, defCluster), sep),
Keyspace: mainflux.Env(envKeyspace, defKeyspace),
Username: mainflux.Env(envDBUsername, defDBUsername),
Password: mainflux.Env(envDBPassword, defDBPassword),
Port: dbPort,
}
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
port: mainflux.Env(envPort, defPort),
cluster: mainflux.Env(envCluster, defCluster),
keyspace: mainflux.Env(envKeyspace, defKeyspace),
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbCfg: dbCfg,
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
func connectToCassandra(dbCfg cassandra.DBConfig, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(dbCfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
os.Exit(1)
@@ -95,17 +155,51 @@ func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Sess
return session
}
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
conn, err := grpc.Dial(url, grpc.WithInsecure())
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
func newService(session *gocql.Session, logger log.Logger) readers.MessageRepository {
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(session *gocql.Session, logger logger.Logger) readers.MessageRepository {
repo := cassandra.New(session)
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(
@@ -127,7 +221,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"))
+88 -26
View File
@@ -9,56 +9,76 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"github.com/BurntSushi/toml"
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/api"
"github.com/mainflux/mainflux/writers/cassandra"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
sep = ","
svcName = "cassandra-writer"
sep = ","
defNatsURL = nats.DefaultURL
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defDBUsername = ""
defDBPassword = ""
defDBPort = "9042"
defChanCfgPath = "/config/channels.toml"
envNatsURL = "MF_NATS_URL"
envPort = "MF_CASSANDRA_WRITER_PORT"
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
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"
envDBUsername = "MF_CASSANDRA_WRITER_DB_USERNAME"
envDBPassword = "MF_CASSANDRA_WRITER_DB_PASSWORD"
envDBPort = "MF_CASSANDRA_WRITER_DB_PORT"
envChanCfgPath = "MF_CASSANDRA_WRITER_CHANNELS_CONFIG"
)
type config struct {
natsURL string
logLevel string
port string
cluster string
keyspace string
dbCfg cassandra.DBConfig
channels map[string]bool
}
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()
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
session := connectToCassandra(cfg.dbCfg, logger)
defer session.Close()
repo := newService(session, logger)
if err := writers.Start(nc, logger, repo); err != nil {
if err := writers.Start(nc, repo, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to create Cassandra writer: %s", err))
}
@@ -72,20 +92,62 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
err := <-errs
err = <-errs
logger.Error(fmt.Sprintf("Cassandra writer service terminated: %s", err))
}
func loadConfig() config {
dbPort, err := strconv.Atoi(mainflux.Env(envDBPort, defDBPort))
if err != nil {
log.Fatal(err)
}
dbCfg := cassandra.DBConfig{
Hosts: strings.Split(mainflux.Env(envCluster, defCluster), sep),
Keyspace: mainflux.Env(envKeyspace, defKeyspace),
Username: mainflux.Env(envDBUsername, defDBUsername),
Password: mainflux.Env(envDBPassword, defDBPassword),
Port: dbPort,
}
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
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),
dbCfg: dbCfg,
channels: loadChansConfig(chanCfgPath),
}
}
func connectToNATS(url string, logger log.Logger) *nats.Conn {
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
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,8 +157,8 @@ func connectToNATS(url string, logger log.Logger) *nats.Conn {
return nc
}
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
func connectToCassandra(dbCfg cassandra.DBConfig, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(dbCfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
os.Exit(1)
@@ -105,10 +167,10 @@ 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(
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(
repo,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "cassandra",
@@ -127,8 +189,8 @@ 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())
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+84 -27
View File
@@ -11,35 +11,30 @@ 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",
ReaderURL: "http://localhost:8905",
ReaderPrefix: "",
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)
},
}
@@ -49,6 +44,7 @@ func main() {
thingsCmd := cli.NewThingsCmd()
channelsCmd := cli.NewChannelsCmd()
messagesCmd := cli.NewMessagesCmd()
provisionCmd := cli.NewProvisionCmd()
// Root Commands
rootCmd.AddCommand(versionCmd)
@@ -56,20 +52,81 @@ func main() {
rootCmd.AddCommand(thingsCmd)
rootCmd.AddCommand(channelsCmd)
rootCmd.AddCommand(messagesCmd)
rootCmd.AddCommand(provisionCmd)
// 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",
)
rootCmd.PersistentFlags().StringVarP(
&cli.Name,
"name",
"n",
"",
"name query parameter",
)
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
+217
View File
@@ -0,0 +1,217 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
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/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
broker "github.com/nats-io/go-nats"
)
const (
defPort = "5683"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defLogLevel = "error"
defClientTLS = "false"
defCACerts = ""
defPingPeriod = "12"
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envPort = "MF_COAP_ADAPTER_PORT"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_COAP_ADAPTER_LOG_LEVEL"
envClientTLS = "MF_COAP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_COAP_ADAPTER_CA_CERTS"
envPingPeriod = "MF_COAP_ADAPTER_PING_PERIOD"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_COAP_ADAPTER_THINGS_TIMEOUT"
)
type config struct {
port string
natsURL string
thingsURL string
logLevel string
clientTLS bool
caCerts string
pingPeriod time.Duration
jaegerURL string
thingsTimeout time.Duration
}
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 := connectToThings(cfg, logger)
defer conn.Close()
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
respChan := make(chan string, 10000)
pubsub := nats.New(nc)
svc := coap.New(pubsub, cc, 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, 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 {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
pp, err := strconv.ParseInt(mainflux.Env(envPingPeriod, defPingPeriod), 10, 64)
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envPingPeriod)
}
if pp < 1 || pp > 24 {
log.Fatalf("Value of %s must be between 1 and 24", envPingPeriod)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
port: mainflux.Env(envPort, defPort),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
pingPeriod: time.Duration(pp),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
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(cfg config, svc coap.Service, auth mainflux.ThingsServiceClient, respChan chan<- string, l logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.port)
l.Info(fmt.Sprintf("CoAP adapter service started, exposed port %s", cfg.port))
errs <- gocoap.ListenAndServe("udp", p, api.MakeCOAPHandler(svc, auth, l, respChan, cfg.pingPeriod))
}
+122 -29
View File
@@ -1,5 +1,5 @@
//
// Copyright (c) 2018
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
@@ -9,65 +9,91 @@ package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"google.golang.org/grpc/credentials"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/mainflux/mainflux"
adapter "github.com/mainflux/mainflux/http"
"github.com/mainflux/mainflux/http/api"
"github.com/mainflux/mainflux/http/nats"
log "github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
"github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
broker "github.com/nats-io/go-nats"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
)
const (
defPort string = "8180"
defNatsURL string = broker.DefaultURL
defThingsURL string = "localhost:8181"
envPort string = "MF_HTTP_ADAPTER_PORT"
envNatsURL string = "MF_NATS_URL"
envThingsURL string = "MF_THINGS_URL"
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defLogLevel = "error"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envClientTLS = "MF_HTTP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_HTTP_ADAPTER_CA_CERTS"
envPort = "MF_HTTP_ADAPTER_PORT"
envLogLevel = "MF_HTTP_ADAPTER_LOG_LEVEL"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_HTTP_ADAPTER_THINGS_TIMEOUT"
)
type config struct {
ThingsURL string
NatsURL string
Port string
thingsURL string
natsURL string
logLevel string
port string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
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)
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 things service: %s", err))
os.Exit(1)
}
conn := connectToThings(cfg, logger)
defer conn.Close()
cc := thingsapi.NewClient(conn)
tracer, closer := initJaeger("http_adapter", cfg.jaegerURL, logger)
defer closer.Close()
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
pub := nats.NewMessagePublisher(nc)
svc := adapter.New(pub)
svc := adapter.New(pub, cc)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
@@ -88,9 +114,9 @@ func main() {
errs := make(chan error, 2)
go func() {
p := fmt.Sprintf(":%s", cfg.Port)
logger.Info(fmt.Sprintf("HTTP adapter service started, exposed port %s", cfg.Port))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc))
p := fmt.Sprintf(":%s", cfg.port)
logger.Info(fmt.Sprintf("HTTP adapter service started on port %s", cfg.port))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, tracer))
}()
go func() {
@@ -102,3 +128,70 @@ func main() {
err = <-errs
logger.Error(fmt.Sprintf("HTTP adapter terminated: %s", err))
}
func loadConfig() config {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
+121 -49
View File
@@ -2,59 +2,86 @@ package main
import (
"fmt"
"io"
"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/readers"
"github.com/mainflux/mainflux/readers/api"
"github.com/mainflux/mainflux/readers/influxdb"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defThingsURL = "localhost:8181"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defThingsURL = "localhost:8181"
defLogLevel = "error"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defClientTLS = "false"
defCACerts = ""
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envThingsURL = "MF_THINGS_URL"
envPort = "MF_INFLUX_READER_PORT"
envDBName = "MF_INFLUX_READER_DB_NAME"
envDBHost = "MF_INFLUX_READER_DB_HOST"
envDBPort = "MF_INFLUX_READER_DB_PORT"
envDBUser = "MF_INFLUX_READER_DB_USER"
envDBPass = "MF_INFLUX_READER_DB_PASS"
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"
envDBPort = "MF_INFLUX_READER_DB_PORT"
envDBUser = "MF_INFLUX_READER_DB_USER"
envDBPass = "MF_INFLUX_READER_DB_PASS"
envClientTLS = "MF_INFLUX_READER_CLIENT_TLS"
envCACerts = "MF_INFLUX_READER_CA_CERTS"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_INFLUX_READER_THINGS_TIMEOUT"
)
type config struct {
ThingsURL string
Port string
DBName string
DBHost string
DBPort string
DBUser string
DBPass string
thingsURL string
logLevel string
port string
dbName string
dbHost string
dbPort string
dbUser string
dbPass string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
func main() {
cfg, clientCfg := loadConfigs()
logger := log.New(os.Stdout)
conn := connectToThings(cfg.ThingsURL, logger)
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
conn := connectToThings(cfg, logger)
defer conn.Close()
tc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
client, err := influxdata.NewHTTPClient(clientCfg)
if err != nil {
@@ -63,11 +90,7 @@ func main() {
}
defer client.Close()
repo, err := influxdb.New(client, cfg.DBName)
if err != nil {
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
os.Exit(1)
}
repo := newService(client, cfg.dbName, logger)
errs := make(chan error, 2)
go func() {
@@ -76,44 +99,93 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
go startHTTPServer(repo, tc, cfg.Port, logger, errs)
go startHTTPServer(repo, tc, cfg.port, logger, errs)
err = <-errs
logger.Error(fmt.Sprintf("InfluxDB writer service terminated: %s", err))
}
func loadConfigs() (config, influxdata.HTTPConfig) {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
cfg := config{
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
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),
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),
dbPort: mainflux.Env(envDBPort, defDBPort),
dbUser: mainflux.Env(envDBUser, defDBUser),
dbPass: mainflux.Env(envDBPass, defDBPass),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
clientCfg := influxdata.HTTPConfig{
Addr: fmt.Sprintf("http://%s:%s", cfg.DBHost, cfg.DBPort),
Username: cfg.DBUser,
Password: cfg.DBPass,
Addr: fmt.Sprintf("http://%s:%s", cfg.dbHost, cfg.dbPort),
Username: cfg.dbUser,
Password: cfg.dbPass,
}
return cfg, clientCfg
}
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
conn, err := grpc.Dial(url, grpc.WithInsecure())
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
func newService(client influxdata.Client, logger log.Logger) readers.MessageRepository {
repo, _ := influxdb.New(client, "mainflux")
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(client influxdata.Client, dbName string, logger logger.Logger) readers.MessageRepository {
repo := influxdb.New(client, dbName)
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(
repo,
@@ -134,7 +206,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"))
+112 -44
View File
@@ -9,55 +9,78 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/BurntSushi/toml"
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/api"
"github.com/mainflux/mainflux/writers/influxdb"
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"
svcName = "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"
defChanCfgPath = "/config/channels.toml"
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"
envChanCfgPath = "MF_INFLUX_WRITER_CHANNELS_CONFIG"
)
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
channels map[string]bool
}
func main() {
cfg, clientCfg := loadConfigs()
logger := log.New(os.Stdout)
nc, err := nats.Connect(cfg.NatsURL)
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))
os.Exit(1)
@@ -71,17 +94,30 @@ 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)
}
counter, latency := makeMetrics()
repo = writers.LoggingMiddleware(repo, logger)
repo = writers.MetricsMiddleware(repo, counter, latency)
if err := writers.Start(nc, logger, repo); err != nil {
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(repo, counter, latency)
if err := writers.Start(nc, repo, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to start InfluxDB writer: %s", err))
os.Exit(1)
}
@@ -92,32 +128,64 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
go startHTTPService(cfg.Port, logger, errs)
go startHTTPService(cfg.port, logger, errs)
err = <-errs
logger.Error(fmt.Sprintf("InfluxDB writer service terminated: %s", err))
}
func loadConfigs() (config, influxdata.HTTPConfig) {
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
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),
channels: loadChansConfig(chanCfgPath),
}
clientCfg := influxdata.HTTPConfig{
Addr: fmt.Sprintf("http://%s:%s", cfg.DBHost, cfg.DBPort),
Username: cfg.DBUser,
Password: cfg.DBPass,
Addr: fmt.Sprintf("http://%s:%s", cfg.dbHost, cfg.dbPort),
Username: cfg.dbUser,
Password: cfg.dbPass,
}
return cfg, clientCfg
}
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "influxdb",
@@ -136,8 +204,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))
errs <- http.ListenAndServe(p, influxdb.MakeHandler())
logger.Info(fmt.Sprintf("InfluxDB writer service started, exposed port %s", p))
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+220
View File
@@ -0,0 +1,220 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
mqtt "github.com/eclipse/paho.mqtt.golang"
r "github.com/go-redis/redis"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/lora"
"github.com/mainflux/mainflux/lora/api"
pub "github.com/mainflux/mainflux/lora/nats"
mqttBroker "github.com/mainflux/mainflux/lora/paho"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/mainflux/mainflux/lora/redis"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
defHTTPPort = "8180"
defLoraMsgURL = "tcp://localhost:1883"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defInstanceName = "lora"
defRouteMapURL = "localhost:6379"
defRouteMapPass = ""
defRouteMapDB = "0"
envHTTPPort = "MF_LORA_ADAPTER_HTTP_PORT"
envLoraMsgURL = "MF_LORA_ADAPTER_MESSAGES_URL"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_LORA_ADAPTER_LOG_LEVEL"
envESURL = "MF_THINGS_ES_URL"
envESPass = "MF_THINGS_ES_PASS"
envESDB = "MF_THINGS_ES_DB"
envInstanceName = "MF_LORA_ADAPTER_INSTANCE_NAME"
envRouteMapURL = "MF_LORA_ADAPTER_ROUTEMAP_URL"
envRouteMapPass = "MF_LORA_ADAPTER_ROUTEMAP_PASS"
envRouteMapDB = "MF_LORA_ADAPTER_ROUTEMAP_DB"
loraServerTopic = "application/+/device/+/rx"
thingsRMPrefix = "thing"
channelsRMPrefix = "channel"
)
type config struct {
httpPort string
loraMsgURL string
natsURL string
logLevel string
esURL string
esPass string
esDB string
instanceName string
routeMapURL string
routeMapPass string
routeMapDB string
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
natsConn := connectToNATS(cfg.natsURL, logger)
defer natsConn.Close()
rmConn := connectToRedis(cfg.routeMapURL, cfg.routeMapPass, cfg.routeMapDB, logger)
defer rmConn.Close()
esConn := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
defer esConn.Close()
publisher := pub.NewMessagePublisher(natsConn)
thingRM := newRouteMapRepositoy(rmConn, thingsRMPrefix, logger)
chanRM := newRouteMapRepositoy(rmConn, channelsRMPrefix, logger)
mqttConn := connectToMQTTBroker(cfg.loraMsgURL, logger)
svc := lora.New(publisher, thingRM, chanRM)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "lora_adapter",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "lora_adapter",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
go subscribeToLoRaBroker(svc, mqttConn, logger)
go subscribeToThingsES(svc, esConn, cfg.instanceName, logger)
errs := make(chan error, 2)
go startHTTPServer(cfg, 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("LoRa adapter terminated: %s", err))
}
func loadConfig() config {
return config{
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
loraMsgURL: mainflux.Env(envLoraMsgURL, defLoraMsgURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
instanceName: mainflux.Env(envInstanceName, defInstanceName),
routeMapURL: mainflux.Env(envRouteMapURL, defRouteMapURL),
routeMapPass: mainflux.Env(envRouteMapPass, defRouteMapPass),
routeMapDB: mainflux.Env(envRouteMapDB, defRouteMapDB),
}
}
func connectToNATS(url string, logger logger.Logger) *nats.Conn {
conn, err := nats.Connect(url)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
os.Exit(1)
}
logger.Info("Connected to NATS")
return conn
}
func connectToMQTTBroker(loraURL string, logger logger.Logger) mqtt.Client {
opts := mqtt.NewClientOptions()
opts.AddBroker(loraURL)
opts.SetUsername("")
opts.SetPassword("")
opts.SetOnConnectHandler(func(c mqtt.Client) {
logger.Info("Connected to Lora MQTT broker")
})
opts.SetConnectionLostHandler(func(c mqtt.Client, err error) {
logger.Error(fmt.Sprintf("MQTT connection lost: %s", err.Error()))
os.Exit(1)
})
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
logger.Error(fmt.Sprintf("Failed to connect to Lora MQTT broker: %s", token.Error()))
os.Exit(1)
}
return client
}
func connectToRedis(redisURL, redisPass, redisDB string, logger logger.Logger) *r.Client {
db, err := strconv.Atoi(redisDB)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to redis: %s", err))
os.Exit(1)
}
return r.NewClient(&r.Options{
Addr: redisURL,
Password: redisPass,
DB: db,
})
}
func subscribeToLoRaBroker(svc lora.Service, mc mqtt.Client, logger logger.Logger) {
mqttBroker := mqttBroker.NewBroker(svc, mc, logger)
logger.Info("Subscribed to Lora MQTT broker")
if err := mqttBroker.Subscribe(loraServerTopic); err != nil {
logger.Error(fmt.Sprintf("Failed to subscribe to Lora MQTT broker: %s", err))
os.Exit(1)
}
}
func subscribeToThingsES(svc lora.Service, client *r.Client, consumer string, logger logger.Logger) {
eventStore := redis.NewEventStore(svc, client, consumer, logger)
logger.Info("Subscribed to Redis Event Store")
if err := eventStore.Subscribe("mainflux.things"); err != nil {
logger.Warn(fmt.Sprintf("Lora-adapter service failed to subscribe to event sourcing: %s", err))
}
}
func newRouteMapRepositoy(client *r.Client, prefix string, logger logger.Logger) lora.RouteMapRepository {
logger.Info("Connected to Redis Route map")
return redis.NewRouteMapRepository(client, prefix)
}
func startHTTPServer(cfg config, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.httpPort)
logger.Info(fmt.Sprintf("Lora-adapter service started, exposed port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, api.MakeHandler())
}
+114 -35
View File
@@ -10,53 +10,82 @@ package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
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"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
"github.com/mongodb/mongo-go-driver/mongo"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defThingsURL = "localhost:8181"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "27017"
defThingsURL = "localhost:8181"
defLogLevel = "error"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "27017"
defClientTLS = "false"
defCACerts = ""
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envThingsURL = "MF_THINGS_URL"
envPort = "MF_MONGO_READER_PORT"
envDBName = "MF_MONGO_READER_DB_NAME"
envDBHost = "MF_MONGO_READER_DB_HOST"
envDBPort = "MF_MONGO_READER_DB_PORT"
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"
envDBPort = "MF_MONGO_READER_DB_PORT"
envClientTLS = "MF_MONGO_READER_CLIENT_TLS"
envCACerts = "MF_MONGO_READER_CA_CERTS"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_MONGO_READER_THINGS_TIMEOUT"
)
type config struct {
thingsURL string
port string
dbName string
dbHost string
dbPort string
thingsURL string
logLevel string
port string
dbName string
dbHost string
dbPort string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
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)
conn := connectToThings(cfg, logger)
defer conn.Close()
tc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
db := connectToMongoDB(cfg.dbHost, cfg.dbPort, cfg.dbName, logger)
@@ -71,22 +100,38 @@ 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 {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
port: mainflux.Env(envPort, defPort),
dbName: mainflux.Env(envDBName, defDBName),
dbHost: mainflux.Env(envDBHost, defDBHost),
dbPort: mainflux.Env(envDBPort, defDBPort),
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),
dbPort: mainflux.Env(envDBPort, defDBPort),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToMongoDB(host, port, name string, logger log.Logger) *mongo.Database {
client, err := mongo.Connect(context.Background(), fmt.Sprintf("mongodb://%s:%s", host, port), nil)
func connectToMongoDB(host, port, name string, logger logger.Logger) *mongo.Database {
addr := fmt.Sprintf("mongodb://%s:%s", host, port)
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to database: %s", err))
os.Exit(1)
@@ -95,17 +140,51 @@ func connectToMongoDB(host, port, name string, logger log.Logger) *mongo.Databas
return client.Database(name)
}
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
conn, err := grpc.Dial(url, grpc.WithInsecure())
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
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,8 +206,8 @@ 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"))
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "mongodb-reader"))
}
+82 -34
View File
@@ -10,68 +10,86 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/BurntSushi/toml"
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/mongodb/mongo-go-driver/mongo"
"github.com/mainflux/mainflux/writers/api"
"github.com/mainflux/mainflux/writers/mongodb"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const (
defNatsURL = nats.DefaultURL
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "27017"
svcName = "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"
defChanCfgPath = "/config/channels.toml"
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"
envChanCfgPath = "MF_MONGO_WRITER_CHANNELS_CONFIG"
)
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
channels map[string]bool
}
func main() {
cfg := loadConfigs()
logger := log.New(os.Stdout)
nc, err := nats.Connect(cfg.NatsURL)
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatal(err)
}
nc, err := nats.Connect(cfg.natsURL)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
os.Exit(1)
}
defer nc.Close()
client, err := mongo.Connect(context.Background(), fmt.Sprintf("mongodb://%s:%s", cfg.DBHost, cfg.DBPort), nil)
addr := fmt.Sprintf("mongodb://%s:%s", cfg.dbHost, cfg.dbPort)
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to database: %s", err))
os.Exit(1)
}
db := client.Database(cfg.DBName)
db := client.Database(cfg.dbName)
repo := mongodb.New(db)
counter, latency := makeMetrics()
repo = writers.LoggingMiddleware(repo, logger)
repo = writers.MetricsMiddleware(repo, counter, latency)
if err := writers.Start(nc, logger, repo); err != nil {
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(repo, counter, latency)
if err := writers.Start(nc, repo, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to start MongoDB writer: %s", err))
os.Exit(1)
}
@@ -82,22 +100,52 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
go startHTTPService(cfg.Port, logger, errs)
go startHTTPService(cfg.port, logger, errs)
err = <-errs
logger.Error(fmt.Sprintf("MongoDB writer service terminated: %s", err))
}
func loadConfigs() config {
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
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),
channels: loadChansConfig(chanCfgPath),
}
}
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "mongodb",
@@ -116,8 +164,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("Mongodb writer service started, exposed port %s", p))
errs <- http.ListenAndServe(p, mongodb.MakeHandler())
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+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),
}
}
+228
View File
@@ -0,0 +1,228 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/readers"
"github.com/mainflux/mainflux/readers/api"
"github.com/mainflux/mainflux/readers/postgres"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
svcName = "postgres-writer"
sep = ","
defThingsURL = "localhost:8183"
defLogLevel = "debug"
defPort = "9204"
defClientTLS = "false"
defCACerts = ""
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "messages"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_POSTGRES_READER_LOG_LEVEL"
envPort = "MF_POSTGRES_READER_PORT"
envClientTLS = "MF_POSTGRES_READER_CLIENT_TLS"
envCACerts = "MF_POSTGRES_READER_CA_CERTS"
envDBHost = "MF_POSTGRES_READER_DB_HOST"
envDBPort = "MF_POSTGRES_READER_DB_PORT"
envDBUser = "MF_POSTGRES_READER_DB_USER"
envDBPass = "MF_POSTGRES_READER_DB_PASS"
envDBName = "MF_POSTGRES_READER_DB_NAME"
envDBSSLMode = "MF_POSTGRES_READER_DB_SSL_MODE"
envDBSSLCert = "MF_POSTGRES_READER_DB_SSL_CERT"
envDBSSLKey = "MF_POSTGRES_READER_DB_SSL_KEY"
envDBSSLRootCert = "MF_POSTGRES_READER_DB_SSL_ROOT_CERT"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_POSTGRES_READER_THINGS_TIMEOUT"
)
type config struct {
thingsURL string
logLevel string
port string
clientTLS bool
caCerts string
dbConfig postgres.Config
jaegerURL string
thingsTimeout time.Duration
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
conn := connectToThings(cfg, logger)
defer conn.Close()
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
repo := newService(db, logger)
errs := make(chan error, 2)
go startHTTPServer(repo, tc, cfg.port, 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("Postgres writer service terminated: %s", err))
}
func loadConfig() config {
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbConfig: dbConfig,
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Postgres: %s", err))
os.Exit(1)
}
return db
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
func newService(db *sqlx.DB, logger logger.Logger) readers.MessageRepository {
svc := postgres.New(db)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "postgres",
Subsystem: "message_writer",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "postgres",
Subsystem: "message_writer",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
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("Postgres reader service started, exposed port %s", port))
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, svcName))
}
+200
View File
@@ -0,0 +1,200 @@
//
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/BurntSushi/toml"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/writers"
"github.com/mainflux/mainflux/writers/api"
"github.com/mainflux/mainflux/writers/postgres"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
svcName = "postgres-writer"
sep = ","
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "9104"
defDBHost = "postgres"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "messages"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defChanCfgPath = "/config/channels.toml"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_POSTGRES_WRITER_LOG_LEVEL"
envPort = "MF_POSTGRES_WRITER_PORT"
envDBHost = "MF_POSTGRES_WRITER_DB_HOST"
envDBPort = "MF_POSTGRES_WRITER_DB_PORT"
envDBUser = "MF_POSTGRES_WRITER_DB_USER"
envDBPass = "MF_POSTGRES_WRITER_DB_PASS"
envDBName = "MF_POSTGRES_WRITER_DB_NAME"
envDBSSLMode = "MF_POSTGRES_WRITER_DB_SSL_MODE"
envDBSSLCert = "MF_POSTGRES_WRITER_DB_SSL_CERT"
envDBSSLKey = "MF_POSTGRES_WRITER_DB_SSL_KEY"
envDBSSLRootCert = "MF_POSTGRES_WRITER_DB_SSL_ROOT_CERT"
envChanCfgPath = "MF_POSTGRES_WRITER_CHANNELS_CONFIG"
)
type config struct {
natsURL string
logLevel string
port string
dbConfig postgres.Config
channels map[string]bool
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
nc := connectToNATS(cfg.natsURL, logger)
defer nc.Close()
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
repo := newService(db, logger)
if err = writers.Start(nc, repo, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to create Postgres writer: %s", err))
}
errs := make(chan error, 2)
go startHTTPServer(cfg.port, errs, logger)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("Postgres writer service terminated: %s", err))
}
func loadConfig() config {
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbConfig: dbConfig,
channels: loadChansConfig(chanCfgPath),
}
}
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
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))
os.Exit(1)
}
return nc
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Postgres: %s", err))
os.Exit(1)
}
return db
}
func newService(db *sqlx.DB, logger logger.Logger) writers.MessageRepository {
svc := postgres.New(db)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "postgres",
Subsystem: "message_writer",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "postgres",
Subsystem: "message_writer",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
func startHTTPServer(port string, errs chan error, logger logger.Logger) {
p := fmt.Sprintf(":%s", port)
logger.Info(fmt.Sprintf("Postgres writer service started, exposed port %s", port))
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+275 -63
View File
@@ -1,5 +1,5 @@
//
// Copyright (c) 2018
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
@@ -8,74 +8,163 @@
package main
import (
"database/sql"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/mainflux/mainflux/things/tracing"
"github.com/jmoiron/sqlx"
opentracing "github.com/opentracing/opentracing-go"
"google.golang.org/grpc/credentials"
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"
httpapi "github.com/mainflux/mainflux/things/api/http"
authgrpcapi "github.com/mainflux/mainflux/things/api/auth/grpc"
authhttpapi "github.com/mainflux/mainflux/things/api/auth/http"
thhttpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/mainflux/mainflux/things/postgres"
rediscache "github.com/mainflux/mainflux/things/redis"
localusers "github.com/mainflux/mainflux/things/users"
"github.com/mainflux/mainflux/things/uuid"
usersapi "github.com/mainflux/mainflux/users/api/grpc"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
)
const (
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "things"
defHTTPPort = "8180"
defGRPCPort = "8181"
defUsersURL = "localhost:8181"
envDBHost = "MF_THINGS_DB_HOST"
envDBPort = "MF_THINGS_DB_PORT"
envDBUser = "MF_THINGS_DB_USER"
envDBPass = "MF_THINGS_DB_PASS"
envDBName = "MF_THINGS_DB"
envHTTPPort = "MF_THINGS_HTTP_PORT"
envGRPCPort = "MF_THINGS_GRPC_PORT"
envUsersURL = "MF_USERS_URL"
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "things"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defClientTLS = "false"
defCACerts = ""
defCacheURL = "localhost:6379"
defCachePass = ""
defCacheDB = "0"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defHTTPPort = "8180"
defAuthHTTPPort = "8989"
defAuthGRPCPort = "8181"
defServerCert = ""
defServerKey = ""
defUsersURL = "localhost:8181"
defSingleUserEmail = ""
defSingleUserToken = ""
defJaegerURL = "localhost:6831"
defUsersTimeout = "1" // in seconds
envLogLevel = "MF_THINGS_LOG_LEVEL"
envDBHost = "MF_THINGS_DB_HOST"
envDBPort = "MF_THINGS_DB_PORT"
envDBUser = "MF_THINGS_DB_USER"
envDBPass = "MF_THINGS_DB_PASS"
envDBName = "MF_THINGS_DB"
envDBSSLMode = "MF_THINGS_DB_SSL_MODE"
envDBSSLCert = "MF_THINGS_DB_SSL_CERT"
envDBSSLKey = "MF_THINGS_DB_SSL_KEY"
envDBSSLRootCert = "MF_THINGS_DB_SSL_ROOT_CERT"
envClientTLS = "MF_THINGS_CLIENT_TLS"
envCACerts = "MF_THINGS_CA_CERTS"
envCacheURL = "MF_THINGS_CACHE_URL"
envCachePass = "MF_THINGS_CACHE_PASS"
envCacheDB = "MF_THINGS_CACHE_DB"
envESURL = "MF_THINGS_ES_URL"
envESPass = "MF_THINGS_ES_PASS"
envESDB = "MF_THINGS_ES_DB"
envHTTPPort = "MF_THINGS_HTTP_PORT"
envAuthHTTPPort = "MF_THINGS_AUTH_HTTP_PORT"
envAuthGRPCPort = "MF_THINGS_AUTH_GRPC_PORT"
envUsersURL = "MF_USERS_URL"
envServerCert = "MF_THINGS_SERVER_CERT"
envServerKey = "MF_THINGS_SERVER_KEY"
envSingleUserEmail = "MF_THINGS_SINGLE_USER_EMAIL"
envSingleUserToken = "MF_THINGS_SINGLE_USER_TOKEN"
envJaegerURL = "MF_JAEGER_URL"
envUsersTimeout = "MF_THINGS_USERS_TIMEOUT"
)
type config struct {
DBHost string
DBPort string
DBUser string
DBPass string
DBName string
HTTPPort string
GRPCPort string
UsersURL string
logLevel string
dbConfig postgres.Config
clientTLS bool
caCerts string
cacheURL string
cachePass string
cacheDB string
esURL string
esPass string
esDB string
httpPort string
authHTTPPort string
authGRPCPort string
usersURL string
serverCert string
serverKey string
singleUserEmail string
singleUserToken string
jaegerURL string
usersTimeout time.Duration
}
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)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cacheClient := connectToRedis(cfg.cacheURL, cfg.cachePass, cfg.cacheDB, logger)
esClient := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
conn := connectToUsersService(cfg.UsersURL, logger)
defer conn.Close()
usersTracer, usersCloser := initJaeger("users", cfg.jaegerURL, logger)
defer usersCloser.Close()
svc := newService(conn, db, logger)
users, close := createUsersClient(cfg, usersTracer, logger)
if close != nil {
defer close()
}
dbTracer, dbCloser := initJaeger("things_db", cfg.jaegerURL, logger)
defer dbCloser.Close()
cacheTracer, cacheCloser := initJaeger("things_cache", cfg.jaegerURL, logger)
defer cacheCloser.Close()
svc := newService(users, dbTracer, cacheTracer, db, cacheClient, esClient, logger)
errs := make(chan error, 2)
go startHTTPServer(svc, cfg.HTTPPort, logger, errs)
go startGRPCServer(svc, cfg.GRPCPort, logger, errs)
go startHTTPServer(thhttpapi.MakeHandler(thingsTracer, svc), cfg.httpPort, cfg, logger, errs)
go startHTTPServer(authhttpapi.MakeHandler(thingsTracer, svc), cfg.authHTTPPort, cfg, logger, errs)
go startGRPCServer(svc, thingsTracer, cfg, logger, errs)
go func() {
c := make(chan os.Signal)
@@ -83,25 +172,93 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
err := <-errs
err = <-errs
logger.Error(fmt.Sprintf("Things service terminated: %s", err))
}
func loadConfig() config {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envUsersTimeout, defUsersTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envUsersTimeout, err.Error())
}
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
DBHost: mainflux.Env(envDBHost, defDBHost),
DBPort: mainflux.Env(envDBPort, defDBPort),
DBUser: mainflux.Env(envDBUser, defDBUser),
DBPass: mainflux.Env(envDBPass, defDBPass),
DBName: mainflux.Env(envDBName, defDBName),
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
cacheURL: mainflux.Env(envCacheURL, defCacheURL),
cachePass: mainflux.Env(envCachePass, defCachePass),
cacheDB: mainflux.Env(envCacheDB, defCacheDB),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
authHTTPPort: mainflux.Env(envAuthHTTPPort, defAuthHTTPPort),
authGRPCPort: mainflux.Env(envAuthGRPCPort, defAuthGRPCPort),
usersURL: mainflux.Env(envUsersURL, defUsersURL),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
singleUserEmail: mainflux.Env(envSingleUserEmail, defSingleUserEmail),
singleUserToken: mainflux.Env(envSingleUserToken, defSingleUserToken),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
usersTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToDB(cfg config, logger log.Logger) *sql.DB {
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToRedis(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: db,
})
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
os.Exit(1)
@@ -109,22 +266,56 @@ func connectToDB(cfg config, logger log.Logger) *sql.DB {
return db
}
func connectToUsersService(usersAddr string, logger log.Logger) *grpc.ClientConn {
conn, err := grpc.Dial(usersAddr, grpc.WithInsecure())
func createUsersClient(cfg config, tracer opentracing.Tracer, logger logger.Logger) (mainflux.UsersServiceClient, func() error) {
if cfg.singleUserEmail != "" && cfg.singleUserToken != "" {
return localusers.NewSingleUserService(cfg.singleUserEmail, cfg.singleUserToken), nil
}
conn := connectToUsers(cfg, logger)
return usersapi.NewClient(tracer, conn, cfg.usersTimeout), conn.Close
}
func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to create tls credentials: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
opts = append(opts, grpc.WithInsecure())
logger.Info("gRPC communication is not encrypted")
}
conn, err := grpc.Dial(cfg.usersURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
os.Exit(1)
}
return conn
}
func newService(conn *grpc.ClientConn, db *sql.DB, logger log.Logger) things.Service {
users := usersapi.NewClient(conn)
thingsRepo := postgres.NewThingRepository(db, logger)
channelsRepo := postgres.NewChannelRepository(db, logger)
func newService(users mainflux.UsersServiceClient, dbTracer opentracing.Tracer, cacheTracer opentracing.Tracer, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service {
thingsRepo := postgres.NewThingRepository(db)
thingsRepo = tracing.ThingRepositoryMiddleware(dbTracer, thingsRepo)
channelsRepo := postgres.NewChannelRepository(db)
channelsRepo = tracing.ChannelRepositoryMiddleware(dbTracer, channelsRepo)
chanCache := rediscache.NewChannelCache(cacheClient)
chanCache = tracing.ChannelCacheMiddleware(cacheTracer, chanCache)
thingCache := rediscache.NewThingCache(cacheClient)
thingCache = tracing.ThingCacheMiddleware(cacheTracer, thingCache)
idp := uuid.New()
svc := things.New(users, thingsRepo, channelsRepo, idp)
svc := things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
svc = rediscache.NewEventStoreMiddleware(svc, esClient)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
@@ -144,20 +335,41 @@ func newService(conn *grpc.ClientConn, db *sql.DB, logger log.Logger) things.Ser
return svc
}
func startHTTPServer(svc things.Service, port string, logger log.Logger, errs chan error) {
func startHTTPServer(handler http.Handler, port string, cfg config, 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))
if cfg.serverCert != "" || cfg.serverKey != "" {
logger.Info(fmt.Sprintf("Things service started using https on port %s with cert %s key %s",
port, cfg.serverCert, cfg.serverKey))
errs <- http.ListenAndServeTLS(p, cfg.serverCert, cfg.serverKey, handler)
return
}
logger.Info(fmt.Sprintf("Things service started using http on port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, handler)
}
func startGRPCServer(svc things.Service, port string, logger log.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
func startGRPCServer(svc things.Service, tracer opentracing.Tracer, cfg config, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.authGRPCPort)
listener, err := net.Listen("tcp", p)
if err != nil {
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", cfg.authGRPCPort, err))
os.Exit(1)
}
server := grpc.NewServer()
mainflux.RegisterThingsServiceServer(server, grpcapi.NewServer(svc))
logger.Info(fmt.Sprintf("Things gRPC service started, exposed port %s", port))
var server *grpc.Server
if cfg.serverCert != "" || cfg.serverKey != "" {
creds, err := credentials.NewServerTLSFromFile(cfg.serverCert, cfg.serverKey)
if err != nil {
logger.Error(fmt.Sprintf("Failed to load things certificates: %s", err))
os.Exit(1)
}
logger.Info(fmt.Sprintf("Things gRPC service started using https on port %s with cert %s key %s",
cfg.authGRPCPort, cfg.serverCert, cfg.serverKey))
server = grpc.NewServer(grpc.Creds(creds))
} else {
logger.Info(fmt.Sprintf("Things gRPC service started using http on port %s", cfg.authGRPCPort))
server = grpc.NewServer()
}
mainflux.RegisterThingsServiceServer(server, authgrpcapi.NewServer(tracer, svc))
errs <- server.Serve(listener)
}
+136 -51
View File
@@ -1,5 +1,5 @@
//
// Copyright (c) 2018
// Copyright (c) 2019
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
@@ -8,17 +8,23 @@
package main
import (
"database/sql"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/mainflux/mainflux/users/tracing"
"google.golang.org/grpc/credentials"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/jmoiron/sqlx"
"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"
@@ -26,53 +32,81 @@ import (
"github.com/mainflux/mainflux/users/bcrypt"
"github.com/mainflux/mainflux/users/jwt"
"github.com/mainflux/mainflux/users/postgres"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
)
const (
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "users"
defHTTPPort = "8180"
defGRPCPort = "8181"
defSecret = "users"
envDBHost = "MF_USERS_DB_HOST"
envDBPort = "MF_USERS_DB_PORT"
envDBUser = "MF_USERS_DB_USER"
envDBPass = "MF_USERS_DB_PASS"
envDBName = "MF_USERS_DB"
envHTTPPort = "MF_USERS_HTTP_PORT"
envGRPCPort = "MF_USERS_GRPC_PORT"
envSecret = "MF_USERS_SECRET"
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "users"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defHTTPPort = "8180"
defGRPCPort = "8181"
defSecret = "users"
defServerCert = ""
defServerKey = ""
defJaegerURL = "localhost:6831"
envLogLevel = "MF_USERS_LOG_LEVEL"
envDBHost = "MF_USERS_DB_HOST"
envDBPort = "MF_USERS_DB_PORT"
envDBUser = "MF_USERS_DB_USER"
envDBPass = "MF_USERS_DB_PASS"
envDBName = "MF_USERS_DB"
envDBSSLMode = "MF_USERS_DB_SSL_MODE"
envDBSSLCert = "MF_USERS_DB_SSL_CERT"
envDBSSLKey = "MF_USERS_DB_SSL_KEY"
envDBSSLRootCert = "MF_USERS_DB_SSL_ROOT_CERT"
envHTTPPort = "MF_USERS_HTTP_PORT"
envGRPCPort = "MF_USERS_GRPC_PORT"
envSecret = "MF_USERS_SECRET"
envServerCert = "MF_USERS_SERVER_CERT"
envServerKey = "MF_USERS_SERVER_KEY"
envJaegerURL = "MF_JAEGER_URL"
)
type config struct {
DBHost string
DBPort string
DBUser string
DBPass string
DBName string
HTTPPort string
GRPCPort string
Secret string
logLevel string
dbConfig postgres.Config
httpPort string
grpcPort string
secret string
serverCert string
serverKey string
jaegerURL string
}
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)
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
svc := newService(db, cfg.Secret, logger)
tracer, closer := initJaeger("users", cfg.jaegerURL, logger)
defer closer.Close()
dbTracer, dbCloser := initJaeger("users_db", cfg.jaegerURL, logger)
defer dbCloser.Close()
svc := newService(db, dbTracer, cfg.secret, logger)
errs := make(chan error, 2)
go startHTTPServer(svc, cfg.HTTPPort, logger, errs)
go startGRPCServer(svc, cfg.GRPCPort, logger, errs)
go startHTTPServer(tracer, svc, cfg.httpPort, cfg.serverCert, cfg.serverKey, logger, errs)
go startGRPCServer(tracer, svc, cfg.grpcPort, cfg.serverCert, cfg.serverKey, logger, errs)
go func() {
c := make(chan os.Signal)
@@ -80,25 +114,57 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
err := <-errs
err = <-errs
logger.Error(fmt.Sprintf("Users service terminated: %s", err))
}
func loadConfig() config {
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
DBHost: mainflux.Env(envDBHost, defDBHost),
DBPort: mainflux.Env(envDBPort, defDBPort),
DBUser: mainflux.Env(envDBUser, defDBUser),
DBPass: mainflux.Env(envDBPass, defDBPass),
DBName: mainflux.Env(envDBName, defDBName),
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
Secret: mainflux.Env(envSecret, defSecret),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
grpcPort: mainflux.Env(envGRPCPort, defGRPCPort),
secret: mainflux.Env(envSecret, defSecret),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
}
}
func connectToDB(cfg config, logger log.Logger) *sql.DB {
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
os.Exit(1)
@@ -106,8 +172,8 @@ func connectToDB(cfg config, logger log.Logger) *sql.DB {
return db
}
func newService(db *sql.DB, secret string, logger log.Logger) users.Service {
repo := postgres.New(db)
func newService(db *sqlx.DB, tracer opentracing.Tracer, secret string, logger logger.Logger) users.Service {
repo := tracing.UserRepositoryMiddleware(postgres.New(db), tracer)
hasher := bcrypt.New()
idp := jwt.New(secret)
@@ -131,20 +197,39 @@ 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(tracer opentracing.Tracer, svc users.Service, port string, certFile string, keyFile 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))
if certFile != "" || keyFile != "" {
logger.Info(fmt.Sprintf("Users service started using https, cert %s key %s, exposed port %s", certFile, keyFile, port))
errs <- http.ListenAndServeTLS(p, certFile, keyFile, httpapi.MakeHandler(svc, tracer, logger))
} else {
logger.Info(fmt.Sprintf("Users service started using http, exposed port %s", port))
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, tracer, logger))
}
}
func startGRPCServer(svc users.Service, port string, logger log.Logger, errs chan error) {
func startGRPCServer(tracer opentracing.Tracer, svc users.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
listener, err := net.Listen("tcp", p)
if err != nil {
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
}
server := grpc.NewServer()
mainflux.RegisterUsersServiceServer(server, grpcapi.NewServer(svc))
var server *grpc.Server
if certFile != "" || keyFile != "" {
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
logger.Error(fmt.Sprintf("Failed to load users certificates: %s", err))
os.Exit(1)
}
logger.Info(fmt.Sprintf("Users gRPC service started using https on port %s with cert %s key %s", port, certFile, keyFile))
server = grpc.NewServer(grpc.Creds(creds))
} else {
logger.Info(fmt.Sprintf("Users gRPC service started using http on port %s", port))
server = grpc.NewServer()
}
mainflux.RegisterUsersServiceServer(server, grpcapi.NewServer(tracer, svc))
logger.Info(fmt.Sprintf("Users gRPC service started, exposed port %s", port))
errs <- server.Serve(listener)
}
+134 -40
View File
@@ -9,63 +9,172 @@ package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/mainflux/mainflux"
log "github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
"github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
adapter "github.com/mainflux/mainflux/ws"
"github.com/mainflux/mainflux/ws/api"
"github.com/mainflux/mainflux/ws/nats"
broker "github.com/nats-io/go-nats"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defPort = "8180"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
envPort = "MF_WS_ADAPTER_PORT"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defLogLevel = "error"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defJaegerURL = "localhost:6831"
defThingsTimeout = "1" // in seconds
envClientTLS = "MF_WS_ADAPTER_CLIENT_TLS"
envCACerts = "MF_WS_ADAPTER_CA_CERTS"
envPort = "MF_WS_ADAPTER_PORT"
envLogLevel = "MF_WS_ADAPTER_LOG_LEVEL"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_WS_ADAPTER_THINGS_TIMEOUT"
)
type config struct {
ThingsURL string
NatsURL string
Port string
clientTLS bool
caCerts string
thingsURL string
natsURL string
logLevel string
port string
jaegerURL string
thingsTimeout time.Duration
}
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)
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)
}
conn := connectToThings(cfg, logger)
defer conn.Close()
cc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
pubsub := nats.New(nc)
svc := newService(pubsub, logger)
errs := make(chan error, 2)
go func() {
p := fmt.Sprintf(":%s", cfg.port)
logger.Info(fmt.Sprintf("WebSocket adapter service started, exposed port %s", cfg.port))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc, logger))
}()
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("WebSocket adapter terminated: %s", err))
}
func loadConfig() config {
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
tpc, err := credentials.NewClientTLSFromFile(cfg.caCerts, "")
if err != nil {
logger.Error(fmt.Sprintf("Failed to load certs: %s", err))
os.Exit(1)
}
opts = append(opts, grpc.WithTransportCredentials(tpc))
}
} else {
logger.Info("gRPC communication is not encrypted")
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.thingsURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
os.Exit(1)
}
return conn
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(pubsub adapter.Service, logger logger.Logger) adapter.Service {
svc := adapter.New(pubsub)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
@@ -84,20 +193,5 @@ func main() {
}, []string{"method"}),
)
errs := make(chan error, 2)
go func() {
p := fmt.Sprintf(":%s", cfg.Port)
logger.Info(fmt.Sprintf("WebSocket adapter service started, exposed port %s", cfg.Port))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc, logger))
}()
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("WebSocket adapter terminated: %s", err))
return svc
}
+71
View File
@@ -0,0 +1,71 @@
# 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 |
| MF_COAP_ADAPTER_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_COAP_ADAPTER_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_COAP_ADAPTER_PING_PERIOD | Hours between 1 and 24 to ping client with ACK message | 12 |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
| MF_COAP_ADAPTER_THINGS_TIMEOUT | Things gRPC request timeout in seconds | 1 |
## 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]
MF_COAP_ADAPTER_CLIENT_TLS: [Flag that indicates if TLS should be turned on]
MF_COAP_ADAPTER_CA_CERTS: [Path to trusted CAs in PEM format]
MF_COAP_ADAPTER_PING_PERIOD: [Hours between 1 and 24 to ping client with ACK message]
MF_JAEGER_URL: [Jaeger server URL]
MF_COAP_ADAPTER_THINGS_TIMEOUT: [Things gRPC request timeout in seconds]
```
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] MF_COAP_ADAPTER_CLIENT_TLS=[Flag that indicates if TLS should be turned on] MF_COAP_ADAPTER_CA_CERTS=[Path to trusted CAs in PEM format] MF_COAP_ADAPTER_PING_PERIOD: [Hours between 1 and 24 to ping client with ACK message] MF_JAEGER_URL=[Jaeger server URL] MF_COAP_ADAPTER_THINGS_TIMEOUT=[Things gRPC request timeout in seconds] $GOBIN/mainflux-coap
```
## Usage
If CoAP adapter is running locally (on default 5683 port), a valid URL would be: `coap://localhost/channels/<channel_id>/messages?authorization=<thing_auth_key>`.
Since CoAP protocol does not support `Authorization` header (option) and options have limited size, in order to send CoAP messages, valid `authorization` value (a valid Thing key) must be present in `Uri-Query` option.
+153
View File
@@ -0,0 +1,153 @@
//
// 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 (
"context"
"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, subtopic and adds subscription to
// service map of subscriptions under given ID.
Subscribe(string, string, 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 {
auth mainflux.ThingsServiceClient
pubsub Broker
obs map[string]*Observer
obsLock sync.Mutex
}
// New instantiates the CoAP adapter implementation.
func New(pubsub Broker, auth mainflux.ThingsServiceClient, responses <-chan string) Service {
as := &adapterService{
auth: auth,
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(ctx context.Context, token string, msg mainflux.RawMessage) error {
if err := svc.pubsub.Publish(ctx, token, msg); err != nil {
switch err {
case broker.ErrConnectionClosed, broker.ErrInvalidConnection:
return ErrFailedConnection
default:
return ErrFailedMessagePublish
}
}
return nil
}
func (svc *adapterService) Subscribe(chanID, subtopic, obsID string, o *Observer) error {
if err := svc.pubsub.Subscribe(chanID, subtopic, 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
+75
View File
@@ -0,0 +1,75 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
package api
import (
"context"
"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(ctx context.Context, token string, msg mainflux.RawMessage) (err error) {
defer func(begin time.Time) {
destChannel := msg.Channel
if msg.Subtopic != "" {
destChannel = fmt.Sprintf("%s.%s", destChannel, msg.Subtopic)
}
message := fmt.Sprintf("Method publish to channel %s took %s to complete", destChannel, 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(ctx, token, msg)
}
func (lm *loggingMiddleware) Subscribe(chanID, subtopic, obsID string, o *coap.Observer) (err error) {
defer func(begin time.Time) {
destChannel := chanID
if subtopic != "" {
destChannel = fmt.Sprintf("%s.%s", destChannel, subtopic)
}
message := fmt.Sprintf("Method subscribe to channel %s for client %s took %s to complete", destChannel, 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, subtopic, 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)
}
+63
View File
@@ -0,0 +1,63 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
package api
import (
"context"
"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(ctx context.Context, token string, 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(ctx, token, msg)
}
func (mm *metricsMiddleware) Subscribe(chanID, subtopic, 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, subtopic, 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)
}
+362
View File
@@ -0,0 +1,362 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"net"
"net/http"
"regexp"
"strings"
"time"
gocoap "github.com/dustin/go-coap"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"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"
)
const protocol = "coap"
var (
errBadRequest = errors.New("bad request")
errBadOption = errors.New("bad option")
errMalformedSubtopic = errors.New("malformed subtopic")
channelRegExp = regexp.MustCompile(`^/?channels/([\w\-]+)/messages(/[^?]*)?(\?.*)?$`)
)
var (
auth mainflux.ThingsServiceClient
logger log.Logger
pingPeriod time.Duration
)
type handler func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message
//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, pp time.Duration) gocoap.Handler {
auth = tc
logger = l
pingPeriod = pp
return mux(svc, responses)
}
func mux(svc coap.Service, responses chan<- string) gocoap.Handler {
return gocoap.FuncHandler(func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message {
path := msg.PathString()
if !channelRegExp.Match([]byte(path)) {
logger.Info(fmt.Sprintf("path %s not found", path))
return &gocoap.Message{
Type: gocoap.NonConfirmable,
Code: gocoap.NotFound,
MessageID: msg.MessageID,
Token: msg.Token,
}
}
// Allow "/" to be a part of the path.
if strings.HasPrefix(path, "/") {
msg.SetPathString(path[1:])
}
switch msg.Code {
case gocoap.GET:
return observe(svc, responses)(conn, addr, msg)
default:
return receive(svc, msg)
}
})
}
func id(msg *gocoap.Message) string {
vars := strings.Split(msg.PathString(), "/")
if len(vars) > 1 {
return vars[1]
}
return ""
}
func subtopic(msg *gocoap.Message) string {
path := msg.PathString()
pos := 0
for i, c := range path {
if c == '/' {
pos++
}
if pos == 2 {
return path[i:]
}
}
return ""
}
func authorize(msg *gocoap.Message, res *gocoap.Message, cid string) (string, 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 "", 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 "", err
}
res.Code = gocoap.InternalServerError
}
return id.GetValue(), nil
}
func fmtSubtopic(msg *gocoap.Message) (string, error) {
subtopic := subtopic(msg)
if subtopic == "" {
return subtopic, nil
}
subtopic = strings.Replace(subtopic, "/", ".", -1)
elems := strings.Split(subtopic, ".")
filteredElems := []string{}
for _, elem := range elems {
if elem == "" {
continue
}
if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) {
return "", errMalformedSubtopic
}
filteredElems = append(filteredElems, elem)
}
subtopic = strings.Join(filteredElems, ".")
return subtopic, nil
}
func receive(svc coap.Service, 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
}
}
chanID := id(msg)
if chanID == "" {
res.Code = gocoap.NotFound
return res
}
subtopic, err := fmtSubtopic(msg)
if err != nil {
res.Code = gocoap.BadRequest
return res
}
publisher, err := authorize(msg, res, chanID)
if err != nil {
res.Code = gocoap.Forbidden
return res
}
rawMsg := mainflux.RawMessage{
Channel: chanID,
Subtopic: subtopic,
Publisher: publisher,
Protocol: protocol,
Payload: msg.Payload,
}
if err := svc.Publish(context.Background(), "", 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)
chanID := id(msg)
if chanID == "" {
res.Code = gocoap.NotFound
return res
}
subtopic, err := fmtSubtopic(msg)
if err != nil {
res.Code = gocoap.BadRequest
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-%s-%s", 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, subtopic, 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
}
notifyMsg.Payload = msg.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. Deafault value of pingPeriod is 12.
t := time.NewTicker(pingPeriod * 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
}
+74
View File
@@ -0,0 +1,74 @@
//
// Copyright (c) 2018
// Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//
// Package nats contains NATS message publisher implementation.
package nats
import (
"context"
"fmt"
"github.com/gogo/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) fmtSubject(chanID, subtopic string) string {
subject := fmt.Sprintf("%s.%s", prefix, chanID)
if subtopic != "" {
subject = fmt.Sprintf("%s.%s", subject, subtopic)
}
return subject
}
func (pubsub *natsPublisher) Publish(_ context.Context, _ string, msg mainflux.RawMessage) error {
data, err := proto.Marshal(&msg)
if err != nil {
return err
}
subject := pubsub.fmtSubject(msg.Channel, msg.Subtopic)
return pubsub.nc.Publish(subject, data)
}
func (pubsub *natsPublisher) Subscribe(chanID, subtopic, obsID string, observer *coap.Observer) error {
subject := pubsub.fmtSubject(chanID, subtopic)
sub, err := pubsub.nc.Subscribe(subject, 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
}
-60
View File
@@ -1,60 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "mainflux-ui"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.png"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.scss"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}
-13
View File
@@ -1,13 +0,0 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
-58
View File
@@ -1,58 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# IDEs and editors
/.idea
/.vscode
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage/*
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
Thumbs.db
#json-server database
db.json
db.*
# System Files
.DS_Store
Thumbs.db
-73
View File
@@ -1,73 +0,0 @@
# Mainflux UI Dashboad
Mainflux UI, dashboard for [Mainflux](https://github.com/mainflux/mainflux) Industrial IoT Messaging and Device Management Server.
> **N.B.** Mainflux UI service is WIP and not suitable for deployment at this moment. You are welcome to contribute and improve it.
> ## Development
>- Follow angular-cli [documentation](https://github.com/angular/angular-cli)
>- Follow [official angular style guide](https://angular.io/styleguide)
## Requirements
You'll need the following software installed to get started.
- [Node](https://nodejs.org/en/) 6 or higher, we recommend current LTS version, together with NPM 3 or higher.
- [Angular-cli](https://github.com/angular/angular-cli) Newest version with Webpack integration
- - Depending on how Node is configured on your machine, you may need to run installation command with `sudo`
- [Git](http://git-scm.com/downloads): Use the installer for your OS.
- Windows users can also try [Git for Windows](http://git-for-windows.github.io/).
- For local Development with [Mainflux composition](https://github.com/mainflux/mainflux) running locally, [Chrome extension for Cross origin](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?utm_source=chrome-app-launcher-info-dialog) is required. Because composition is running on different port then our Angular app, we have cross origin.
## Configuration
Change into the directory.
```bash
cd dashflux
```
Install the dependencies. If you're running Mac OS or Linux, you may need to run `sudo npm install` instead, depending on how your machine is configured.
```bash
npm install
```
Set appropriate endpoint URLs in **environment.ts** (for local development will probably be 0.0.0.0:<_port_>) or **environment.prod.ts** for production.
To start the server, run:
```bash
ng serve
```
This will run and assemble our app.
**Now go to `localhost:4200` in your browser to see it in action.**
## Deployment
Dashflux is distributed as Docker container. We use nginx to serve dashflux from docker container, supporting environments using docker multi-stage builds.
Dashflux docker image is available on [Dockerhub mainflux/dashflux](https://hub.docker.com/r/mainflux/dashflux/)
If you want to build image locally, you can build image using the **development** environment:
```bash
docker build -f docker/Dockerfile -t dashflux:dev --build-arg env=dev .
```
Build image using the **production** environment:
```bash
docker build -t dashflux:prod -f ./docker/Dockerfile .
```
**Note:** before running *docker build* command, please make sure appropriate endpoint URLs in *environment.ts* or *environment.prod.ts* are set up.
You can test image running
```bash
docker run -p 80:80 dashflux:dev
```
This will run dashflux in docker container.
Now go to `http://localhost` in your browser to see it in action.
-1
View File
@@ -1 +0,0 @@
node_modules
-13
View File
@@ -1,13 +0,0 @@
# Stage 0, based on Node.js, to build and compile Angular
FROM node:9.5 as node
WORKDIR /app
COPY package.json /app/
RUN npm install
COPY ./ /app/
ARG env=prod
RUN npm run build -- --prod --environment $env
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1.13-alpine
COPY --from=node /app/dist/ /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
-14
View File
@@ -1,14 +0,0 @@
import { AppPage } from './app.po';
describe('mainflux-ui App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});
-11
View File
@@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
-14
View File
@@ -1,14 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
-33
View File
@@ -1,33 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
singleRun: false
});
};
-10394
View File
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
{
"name": "mainflux-ui",
"version": "0.1.0",
"license": "Apache License, version 2.0",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy-config.json",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.4",
"@angular/common": "^5.2.4",
"@angular/compiler": "^5.2.4",
"@angular/core": "^5.2.4",
"@angular/flex-layout": "2.0.0-beta.10-4905443",
"@angular/forms": "^5.2.4",
"@angular/http": "^5.2.4",
"@angular/platform-browser": "^5.2.4",
"@angular/platform-browser-dynamic": "^5.2.4",
"@angular/router": "^5.2.4",
"core-js": "^2.4.1",
"mobx": "^3.5.1",
"mobx-angular": "^2.1.1",
"ngx-auth": "^2.2.0",
"rxjs": "^5.5.2",
"zone.js": "^0.8.14"
},
"devDependencies": {
"@angular/cdk": "^5.2.0",
"@angular/cli": "^1.7.3",
"@angular/compiler-cli": "^5.2.4",
"@angular/language-service": "^5.2.4",
"@angular/material": "^5.2.0",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "^6.0.100",
"codelyzer": "~3.2.0",
"hammerjs": "^2.0.8",
"jasmine-core": "~2.5.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.4",
"protractor": "~5.1.2",
"ts-node": "~3.2.0",
"tslint": "~5.7.0",
"typescript": "~2.4.2"
}
}
-28
View File
@@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
-9
View File
@@ -1,9 +0,0 @@
{
"/api/*": {
"target": "http://localhost:8180",
"secure":false,
"pathRewrite": {
"^/api": ""
}
}
}
-35
View File
@@ -1,35 +0,0 @@
/*
* Copyright (c) 2018
* Mainflux
*
* SPDX-License-Identifier: Apache-2.0
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { Route, RouterModule } from '@angular/router';
import { ProtectedGuard, PublicGuard } from 'ngx-auth';
import { LoginComponent } from './components/auth/login/login.component';
import { SignupComponent } from './components/auth/signup/signup.component';
import { ChannelsComponent } from './components/channels/channels.component';
import { ThingsComponent } from './components/things/things.component';
const routes: Route[] = [
{ path: '', redirectTo: 'things', pathMatch: 'full'},
{ path: 'login', component: LoginComponent, canActivate: [PublicGuard]},
{ path: 'signup', component: SignupComponent, canActivate: [PublicGuard]},
{ path: 'things', component: ThingsComponent, canActivate: [ProtectedGuard]},
{ path: 'channels', component: ChannelsComponent, canActivate: [ProtectedGuard]}
];
@NgModule({
imports: [
CommonModule,
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule { }
-22
View File
@@ -1,22 +0,0 @@
<!--
Copyright (c) 2018
Mainflux
SPDX-License-Identifier: Apache-2.0
-->
<div *mobxAutorun>
<div class="loading" *ngIf="uiStore.loading" fxLayoutAlign="center center">
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
</div>
<mat-toolbar color="primary" class="mat-elevation-z6">
<h1 fxFlex>Mainflux</h1>
<ng-container *ngIf="authStore.isAuthenticated">
<a [routerLink]="['/things']" mat-button>Things</a>
<a [routerLink]="['/channels']" mat-button>Channels</a>
<a (click)="logout()" mat-button>Logout</a>
</ng-container>
</mat-toolbar>
<router-outlet></router-outlet>
</div>
-13
View File
@@ -1,13 +0,0 @@
/*
* Copyright (c) 2018
* Mainflux
*
* SPDX-License-Identifier: Apache-2.0
*/
.loading {
position: absolute;
width: 100%;
height: 100%;
z-index: 999;
}
-47
View File
@@ -1,47 +0,0 @@
/*
* Copyright (c) 2018
* Mainflux
*
* SPDX-License-Identifier: Apache-2.0
*/
import { HttpClientModule } from '@angular/common/http';
import { async, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { MaterialModule } from './core/material/material.module';
import { AuthenticationService } from './core/services/auth/authentication.service';
import { TokenStorage } from './core/services/auth/token-storage.service';
import { ChannelsService } from './core/services/channels/channels.service';
import { ThingsService } from './core/services/things/things.service';
import { UiStore } from './core/store/ui.store';
import { AuthStore } from './core/store/auth.store';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
imports: [
MaterialModule,
HttpClientModule,
RouterTestingModule
],
providers: [
UiStore,
AuthStore,
AuthenticationService,
TokenStorage,
ThingsService,
ChannelsService,
]
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
});
-40
View File
@@ -1,40 +0,0 @@
/*
* Copyright (c) 2018
* Mainflux
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material';
import { reaction } from 'mobx';
import { UiStore } from './core/store/ui.store';
import { AuthStore } from './core/store/auth.store';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor(
private snackBar: MatSnackBar,
public uiStore: UiStore,
public authStore: AuthStore,
) { }
ngOnInit() {
reaction(() => this.authStore.authError, (authError) => {
if (authError) {
this.snackBar.open(authError, '', {
duration: 3000
});
}
});
}
logout() {
this.authStore.logout();
}
}

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