1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-26 13:48:53 +08:00
Mirko Teodorovic 7195cad0f6
MF-397 - Introduce Thing Groups (#1259)
* add things group

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

* add things group repository

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

* add things group repository

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

* add things group repository

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

* add parents and children methods

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

* add parents and children methods

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

* fix logging message and temporary test fix

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

* fix bootstrap test fail

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

* move groups to pkg

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

* move groups to pkg

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

* move groups to pkg

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

* move groups to internal

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

* move groups to internal

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

* fix import

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

* fix linter errors

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

* fix comments

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

* resolve comments, add hierarchy info when retrieving groups

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

* code refactor, separate http into multiple files

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

* code refactor, separate http into multiple files

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

* fix group update

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

* use user id when saving

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

* use user id when saving

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

* rename methods

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

* move code

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

* move code

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

* remove temporary test

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

* add groups test

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

* fix comments, fix responses in api for groups

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

* revert changes

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

add checks for name length

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

fix validation

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

small change to response

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

add comment for Level

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

add level to limit hierarchy retrieval

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

tidy vendor

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

add level to limit hierarchy retrieval

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

mod tidy

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

revert

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

resolve comments

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

small naming and code organize refactor

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

revert Member type

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

fix typo

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

use ltree

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

use ltree

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

add level for retrieving

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

add level

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

add ltre

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

upgrade postgres version in test

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

add ltre

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

remove test for now

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

minor fixes

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

fix id setting

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

fix tree endpoint

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

minor style changes

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

* small changes, adding new lines

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

* minor changes

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

* change function signature

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

* change primary key

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

* change function signature

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

* simplufy code

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

* remove groups test for now

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

* fix tabulation

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

* fix whitespace

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

* remove white space, fix grammar

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

* fix sqls so that not additional retrieve is needed

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

* fix sqls so that not additional retrieve is needed

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

fix primary key

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

remove retrive by name

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

* remove name and parent update

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

* fix cound sql

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

* add line

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

* simplify sql, fix table name

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

* add date

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

* add date

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

* add timestamp to api

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

* fix var name

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

* fix var name

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-11-23 11:34:29 +01:00

382 lines
12 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package main
import (
"fmt"
"io"
"io/ioutil"
"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"
authapi "github.com/mainflux/mainflux/authn/api/grpc"
"github.com/mainflux/mainflux/logger"
uuidProvider "github.com/mainflux/mainflux/pkg/uuid"
"github.com/mainflux/mainflux/things"
"github.com/mainflux/mainflux/things/api"
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"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
)
const (
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDB = "things"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defClientTLS = "false"
defCACerts = ""
defCacheURL = "localhost:6379"
defCachePass = ""
defCacheDB = "0"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defHTTPPort = "8182"
defAuthHTTPPort = "8180"
defAuthGRPCPort = "8181"
defServerCert = ""
defServerKey = ""
defSingleUserEmail = ""
defSingleUserToken = ""
defJaegerURL = ""
defAuthnURL = "localhost:8181"
defAuthnTimeout = "1s"
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"
envDB = "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"
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"
envAuthnURL = "MF_AUTHN_GRPC_URL"
envAuthnTimeout = "MF_AUTHN_GRPC_TIMEOUT"
)
type config struct {
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
serverCert string
serverKey string
singleUserEmail string
singleUserToken string
jaegerURL string
authnURL string
authnTimeout time.Duration
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
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()
authTracer, authCloser := initJaeger("auth", cfg.jaegerURL, logger)
defer authCloser.Close()
auth, close := createAuthClient(cfg, authTracer, 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(auth, dbTracer, cacheTracer, db, cacheClient, esClient, logger)
errs := make(chan error, 2)
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)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
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)
}
authnTimeout, err := time.ParseDuration(mainflux.Env(envAuthnTimeout, defAuthnTimeout))
if err != nil {
log.Fatalf("Invalid %s value: %s", envAuthnTimeout, 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(envDB, defDB),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
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),
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),
authnURL: mainflux.Env(envAuthnURL, defAuthnURL),
authnTimeout: authnTimeout,
}
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
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)
}
return db
}
func createAuthClient(cfg config, tracer opentracing.Tracer, logger logger.Logger) (mainflux.AuthNServiceClient, func() error) {
if cfg.singleUserEmail != "" && cfg.singleUserToken != "" {
return localusers.NewSingleUserService(cfg.singleUserEmail, cfg.singleUserToken), nil
}
conn := connectToAuth(cfg, logger)
return authapi.NewClient(tracer, conn, cfg.authnTimeout), conn.Close
}
func connectToAuth(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.authnURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to authn service: %s", err))
os.Exit(1)
}
return conn
}
func newService(auth mainflux.AuthNServiceClient, dbTracer opentracing.Tracer, cacheTracer opentracing.Tracer, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service {
database := postgres.NewDatabase(db)
thingsRepo := postgres.NewThingRepository(database)
thingsRepo = tracing.ThingRepositoryMiddleware(dbTracer, thingsRepo)
channelsRepo := postgres.NewChannelRepository(database)
channelsRepo = tracing.ChannelRepositoryMiddleware(dbTracer, channelsRepo)
groupsRepo := postgres.NewGroupRepo(database)
groupsRepo = tracing.GroupRepositoryMiddleware(dbTracer, groupsRepo)
chanCache := rediscache.NewChannelCache(cacheClient)
chanCache = tracing.ChannelCacheMiddleware(cacheTracer, chanCache)
thingCache := rediscache.NewThingCache(cacheClient)
thingCache = tracing.ThingCacheMiddleware(cacheTracer, thingCache)
up := uuidProvider.New()
svc := things.New(auth, thingsRepo, channelsRepo, groupsRepo, chanCache, thingCache, up)
svc = rediscache.NewEventStoreMiddleware(svc, esClient)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "things",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "things",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
func startHTTPServer(handler http.Handler, port string, cfg config, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
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, 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", cfg.authGRPCPort, err))
os.Exit(1)
}
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)
}