MG-2187 - Simplify Magistrala core repository (#2338)

Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
This commit is contained in:
Dušan Borovčanin
2024-07-15 17:38:48 +02:00
committed by GitHub
parent ffba4ebb8e
commit 5412bddfcf
160 changed files with 15 additions and 17985 deletions
+2 -80
View File
@@ -20,7 +20,6 @@ on:
- "provision/api/**"
- "readers/api/**"
- "things/api/**"
- "twins/api/**"
- "users/api/**"
env:
@@ -36,15 +35,9 @@ env:
AUTH_URL: http://localhost:8189
BOOTSTRAP_URL: http://localhost:9013
CERTS_URL: http://localhost:9019
TWINS_URL: http://localhost:9018
PROVISION_URL: http://localhost:9016
CASSANDRA_READER_URL: http://localhost:9003
INFLUX_READER_URL: http://localhost:9005
MONGO_READER_URL: http://localhost:9007
POSTGRES_READER_URL: http://localhost:9009
TIMESCALE_READER_URL: http://localhost:9011
SMPP_NOTIFIER_URL: http://localhost:9014
SMTP_NOTIFIER_URL: http://localhost:9015
JOURNAL_URL: http://localhost:9021
jobs:
@@ -84,12 +77,12 @@ jobs:
- ".github/workflows/api-tests.yml"
- "api/openapi/journal.yml"
- "journal/api/**"
auth:
- ".github/workflows/api-tests.yml"
- "api/openapi/auth.yml"
- "auth/api/http/**"
bootstrap:
- ".github/workflows/api-tests.yml"
- "api/openapi/bootstrap.yml"
@@ -100,11 +93,6 @@ jobs:
- "api/openapi/certs.yml"
- "certs/api/**"
notifiers:
- ".github/workflows/api-tests.yml"
- "api/openapi/notifiers.yml"
- "consumers/notifiers/api/**"
http:
- ".github/workflows/api-tests.yml"
- "api/openapi/http.yml"
@@ -130,11 +118,6 @@ jobs:
- "api/openapi/things.yml"
- "things/api/**"
twins:
- ".github/workflows/api-tests.yml"
- "api/openapi/twins.yml"
- "twins/api/**"
users:
- ".github/workflows/api-tests.yml"
- "api/openapi/users.yml"
@@ -200,7 +183,6 @@ jobs:
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Bootstrap API tests
if: steps.changes.outputs.bootstrap == 'true'
uses: schemathesis/action@v1
@@ -221,16 +203,6 @@ jobs:
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Twins API tests
if: steps.changes.outputs.twins == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/twins.yml
base-url: ${{ env.TWINS_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Provision API tests
if: steps.changes.outputs.provision == 'true'
uses: schemathesis/action@v1
@@ -247,36 +219,6 @@ jobs:
make cli
./build/cli provision test
- name: Run Cassandra Reader API tests
if: steps.changes.outputs.readers == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/readers.yml
base-url: ${{ env.CASSANDRA_READER_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Influx Reader API tests
if: steps.changes.outputs.readers == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/readers.yml
base-url: ${{ env.INFLUX_READER_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Mongo Reader API tests
if: steps.changes.outputs.readers == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/readers.yml
base-url: ${{ env.MONGO_READER_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run Postgres Reader API tests
if: steps.changes.outputs.readers == 'true'
uses: schemathesis/action@v1
@@ -297,26 +239,6 @@ jobs:
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run SMPP Notifier API tests
if: steps.changes.outputs.notifiers == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/notifiers.yml
base-url: ${{ env.SMPP_NOTIFIER_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Run SMTP Notifier API tests
if: steps.changes.outputs.notifiers == 'true'
uses: schemathesis/action@v1
with:
schema: api/openapi/notifiers.yml
base-url: ${{ env.SMTP_NOTIFIER_URL }}
checks: all
report: false
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links'
- name: Stop containers
if: always()
run: make run down args="-v" && make run_addons down args="-v"
@@ -72,9 +72,6 @@ jobs:
- "certs/certs.go"
- "certs/pki/vault.go"
- "certs/service.go"
- "twins/twins.go"
- "twins/states.go"
- "twins/service.go"
- "journal/journal.go"
- "magistrala/auth_grpc.pb.go"
@@ -148,17 +145,12 @@ jobs:
mv ./users/mocks/hasher.go ./users/mocks/hasher.go.tmp
mv ./mqtt/mocks/events.go ./mqtt/mocks/events.go.tmp
mv ./readers/mocks/messages.go ./readers/mocks/messages.go.tmp
mv ./lora/mocks/routes.go ./lora/mocks/routes.go.tmp
mv ./consumers/notifiers/mocks/notifier.go ./consumers/notifiers/mocks/notifier.go.tmp
mv ./consumers/notifiers/mocks/service.go ./consumers/notifiers/mocks/service.go.tmp
mv ./consumers/notifiers/mocks/repository.go ./consumers/notifiers/mocks/repository.go.tmp
mv ./certs/mocks/certs.go ./certs/mocks/certs.go.tmp
mv ./certs/mocks/pki.go ./certs/mocks/pki.go.tmp
mv ./certs/mocks/service.go ./certs/mocks/service.go.tmp
mv ./twins/mocks/service.go ./twins/mocks/service.go.tmp
mv ./twins/mocks/states.go ./twins/mocks/states.go.tmp
mv ./twins/mocks/repository.go ./twins/mocks/repository.go.tmp
mv ./twins/mocks/cache.go ./twins/mocks/cache.go.tmp
mv ./journal/mocks/repository.go ./journal/mocks/repository.go.tmp
mv ./journal/mocks/service.go ./journal/mocks/service.go.tmp
mv ./auth/mocks/auth_client.go ./auth/mocks/auth_client.go.tmp
@@ -202,17 +194,12 @@ jobs:
check_mock_changes ./users/mocks/hasher.go "Users Hasher ./users/mocks/hasher.go"
check_mock_changes ./mqtt/mocks/events.go "MQTT Events Store ./mqtt/mocks/events.go"
check_mock_changes ./readers/mocks/messages.go "Message Readers ./readers/mocks/messages.go"
check_mock_changes ./lora/mocks/routes.go "LoRa Repository ./lora/mocks/routes.go"
check_mock_changes ./consumers/notifiers/mocks/notifier.go "Notifiers Notifier ./consumers/notifiers/mocks/notifier.go"
check_mock_changes ./consumers/notifiers/mocks/service.go "Notifiers Service ./consumers/notifiers/mocks/service.go"
check_mock_changes ./consumers/notifiers/mocks/repository.go "Notifiers Repository ./consumers/notifiers/mocks/repository.go"
check_mock_changes ./certs/mocks/certs.go "Certs Repository ./certs/mocks/certs.go"
check_mock_changes ./certs/mocks/pki.go "PKI ./certs/mocks/pki.go"
check_mock_changes ./certs/mocks/service.go "Certs Service ./certs/mocks/service.go"
check_mock_changes ./twins/mocks/service.go "Twins Service ./twins/mocks/service.go"
check_mock_changes ./twins/mocks/states.go "Twins States ./twins/mocks/states.go"
check_mock_changes ./twins/mocks/repository.go "Twins Repository ./twins/mocks/repository.go"
check_mock_changes ./twins/mocks/cache.go "Twins Cache ./twins/mocks/cache.go"
check_mock_changes ./journal/mocks/repository.go "Journal Repository ./journal/mocks/repository.go"
check_mock_changes ./journal/mocks/service.go "Journal Service ./journal/mocks/service.go"
check_mock_changes ./auth/mocks/auth_client.go "Auth Service Client ./auth/mocks/auth_client.go"
+1 -44
View File
@@ -108,9 +108,6 @@ jobs:
consumers:
- "consumers/**"
- "cmd/cassandra-writer/**"
- "cmd/influxdb-writer/**"
- "cmd/mongodb-writer/**"
- "cmd/postgres-writer/**"
- "cmd/timescale-writer/**"
- "cmd/smpp-notifier/**"
@@ -149,12 +146,7 @@ jobs:
- "auth_grpc.pb.go"
- "auth/**"
- "pkg/sdk/**"
lora:
- "lora/**"
- "cmd/lora/**"
- "pkg/messaging/**"
logger:
- "logger/**"
@@ -168,11 +160,6 @@ jobs:
- "logger/**"
- "pkg/events/**"
opcua:
- "opcua/**"
- "cmd/opcua/**"
- "logger/**"
pkg-auth:
- "pkg/auth/**"
@@ -207,7 +194,6 @@ jobs:
- "provision/**"
- "readers/**"
- "things/**"
- "twins/**"
- "users/**"
pkg-transformers:
@@ -227,9 +213,6 @@ jobs:
readers:
- "readers/**"
- "cmd/cassandra-reader/**"
- "cmd/influxdb-reader/**"
- "cmd/mongodb-reader/**"
- "cmd/postgres-reader/**"
- "cmd/timescale-reader/**"
- "auth.pb.go"
@@ -248,17 +231,6 @@ jobs:
- "pkg/uuid/**"
- "pkg/events/**"
twins:
- "twins/**"
- "cmd/twins/**"
- "auth.pb.go"
- "auth_grpc.pb.go"
- "auth/**"
- "pkg/messaging/**"
- "pkg/ulid/**"
- "pkg/uuid/**"
- "logger/**"
users:
- "users/**"
- "cmd/users/**"
@@ -337,21 +309,11 @@ jobs:
run: |
go test --race -v -count=1 -coverprofile=coverage/logger.out ./logger/...
- name: Run LoRa tests
if: steps.changes.outputs.lora == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/lora.out ./lora/...
- name: Run MQTT tests
if: steps.changes.outputs.mqtt == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/mqtt.out ./mqtt/...
- name: Run OPC-UA tests
if: steps.changes.outputs.opcua == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/opcua.out ./opcua/...
- name: Run pkg auth tests
if: steps.changes.outputs.pkg-auth == 'true' || steps.changes.outputs.workflow == 'true'
run: |
@@ -412,11 +374,6 @@ jobs:
run: |
go test --race -v -count=1 -coverprofile=coverage/things.out ./things/...
- name: Run twins tests
if: steps.changes.outputs.twins == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/twins.out ./twins/...
- name: Run users tests
if: steps.changes.outputs.users == 'true' || steps.changes.outputs.workflow == 'true'
run: |
+6 -12
View File
@@ -3,10 +3,9 @@
MG_DOCKER_IMAGE_NAME_PREFIX ?= magistrala
BUILD_DIR = build
SERVICES = auth users things http coap ws lora influxdb-writer influxdb-reader mongodb-writer \
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader timescale-writer timescale-reader cli \
bootstrap opcua twins mqtt provision certs smtp-notifier smpp-notifier invitations journal
TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things twins users
SERVICES = auth users things http coap ws postgres-writer postgres-reader timescale-writer \
timescale-reader cli bootstrap mqtt provision certs invitations journal
TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things users
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
@@ -70,10 +69,7 @@ define make_docker_dev
-f docker/Dockerfile.dev ./build
endef
ADDON_SERVICES = bootstrap cassandra-reader cassandra-writer certs \
influxdb-reader influxdb-writer lora-adapter mongodb-reader mongodb-writer \
opcua-adapter postgres-reader postgres-writer provision smpp-notifier smtp-notifier \
timescale-reader timescale-writer twins journal
ADDON_SERVICES = bootstrap journal provision certs timescale-reader timescale-writer postgres-reader postgres-writer
EXTERNAL_SERVICES = vault prometheus
@@ -124,13 +120,13 @@ mocks:
mockery --config ./tools/config/mockery.yaml
DIRS = consumers readers postgres internal opcua
DIRS = consumers readers postgres internal
test: mocks
mkdir -p coverage
@for dir in $(DIRS); do \
go test -v --race -count 1 -tags test -coverprofile=coverage/$$dir.out $$(go list ./... | grep $$dir | grep -v 'cmd'); \
done
go test -v --race -count 1 -tags test -coverprofile=coverage/coverage.out $$(go list ./... | grep -v 'consumers\|readers\|postgres\|internal\|opcua\|cmd')
go test -v --race -count 1 -tags test -coverprofile=coverage/coverage.out $$(go list ./... | grep -v 'consumers\|readers\|postgres\|internal\|cmd')
define test_api_service
$(eval svc=$(subst test_api_,,$(1)))
@@ -174,10 +170,8 @@ test_api_invitations: TEST_API_URL := http://localhost:9020
test_api_auth: TEST_API_URL := http://localhost:8189
test_api_bootstrap: TEST_API_URL := http://localhost:9013
test_api_certs: TEST_API_URL := http://localhost:9019
test_api_twins: TEST_API_URL := http://localhost:9018
test_api_provision: TEST_API_URL := http://localhost:9016
test_api_readers: TEST_API_URL := http://localhost:9009 # This can be the URL of any reader service.
test_api_notifiers: TEST_API_URL := http://localhost:9014 # This can be the URL of any notifier service.
test_api_journal: TEST_API_URL := http://localhost:9021
$(TEST_API):
+6 -6
View File
@@ -15,19 +15,18 @@ Magistrala is modern, scalable, secure, open-source, and patent-free IoT cloud p
It accepts user and thing (sensor, actuator, application) 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].
For extra bits and services see [our contrib repository][contrib].
## Features
- Multi-protocol connectivity and bridging (HTTP, MQTT, WebSocket and CoAP)
- Multi-protocol connectivity and bridging (HTTP, MQTT, WebSocket and CoAP; see [contrib repository][contrib] for LoRa and OPC UA)
- Device management and provisioning (Zero Touch provisioning)
- Mutual TLS Authentication (mTLS) using X.509 Certificates
- Fine-grained access control (policies, ABAC/RBAC)
- Message persistence (Cassandra, InfluxDB, MongoDB and PostgresSQL)
- Message persistence (Timescale and PostgresSQL - see [contrib repository][contrib] for Cassandra, InfluxDB, and MongoDB support)
- Platform logging and instrumentation support (Prometheus and OpenTelemetry)
- Event sourcing
- Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes]
- [LoRaWAN][lora] network integration
- [OPC UA][opcua] integration
- Edge [Agent][agent] and [Export][export] services for remote IoT gateway management and edge computing
- SDK
- CLI
@@ -120,6 +119,8 @@ Thank you for your interest in Magistrala and the desire to contribute!
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
Also, explore our [contrib][contrib] repository for extra services such as Cassandra, InfluxDB, MongoDB readers and writers, LoRa, OPC UA support, Digital Twins, and more. If you have a contribution that is not a good fit for the core monorepo (it's specific to your use case, it's an additional feature or a new service, it's optional or an add-on), this is a great place to submit the pull request.
### We're Hiring
You like Magistrala and you would like to make it your day job? We're always looking for talented engineers interested in open-source, IoT and distributed systems. If you recognize yourself, reach out to [@drasko][drasko] - he will contact you back.
@@ -167,8 +168,6 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w
[cov-url]: https://codecov.io/gh/absmach/magistrala
[license]: https://img.shields.io/badge/license-Apache%20v2.0-blue.svg
[twitter]: https://twitter.com/absmach
[lora]: https://lora-alliance.org/
[opcua]: https://opcfoundation.org/about/opc-technologies/opc-ua/
[agent]: https://github.com/absmach/agent
[export]: https://github.com/absmach/export
[kubernetes]: https://kubernetes.io/
@@ -189,3 +188,4 @@ By utilizing Magistrala, you actively contribute to its improvement. Together, w
[mirko]: https://github.com/mteodor
[rodneyosodo]: https://github.com/rodneyosodo
[callhome]: https://deployments.magistrala.abstractmachines.fr/
[contrib]: https://www.github.com/absmach/mg-contrib
-152
View File
@@ -1,152 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains cassandra-reader main function to start the cassandra-reader service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
cassandraclient "github.com/absmach/magistrala/internal/clients/cassandra"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/readers"
"github.com/absmach/magistrala/readers/api"
"github.com/absmach/magistrala/readers/cassandra"
"github.com/caarlos0/env/v10"
"github.com/gocql/gocql"
"golang.org/x/sync/errgroup"
)
const (
svcName = "cassandra-reader"
envPrefixDB = "MG_CASSANDRA_"
envPrefixHTTP = "MG_CASSANDRA_READER_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
envPrefixAuthz = "MG_THINGS_AUTH_GRPC_"
defSvcHTTPPort = "9003"
)
type config struct {
LogLevel string `env:"MG_CASSANDRA_READER_LOG_LEVEL" envDefault:"info"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_CASSANDRA_READER_INSTANCE_ID" envDefault:""`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
// Create cassandra reader service configurations
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s service configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
ac, acHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer acHandler.Close()
logger.Info("Successfully connected to auth grpc server " + acHandler.Secure())
authConfig = auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuthz}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
tc, tcHandler, err := auth.SetupAuthz(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer tcHandler.Close()
logger.Info("Successfully connected to things grpc server " + tcHandler.Secure())
// Create new cassandra client
csdSession, err := cassandraclient.Setup(envPrefixDB)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer csdSession.Close()
// Create new service
repo := newService(csdSession, logger)
// Create new http server
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, ac, tc, svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
// Start servers
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("Cassandra reader service terminated: %s", err))
}
}
func newService(csdSession *gocql.Session, logger *slog.Logger) readers.MessageRepository {
repo := cassandra.New(csdSession)
repo = api.LoggingMiddleware(repo, logger)
counter, latency := prometheus.MakeMetrics("cassandra", "message_reader")
repo = api.MetricsMiddleware(repo, counter, latency)
return repo
}
-155
View File
@@ -1,155 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains cassandra-writer main function to start the cassandra-writer service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/consumers"
consumertracing "github.com/absmach/magistrala/consumers/tracing"
"github.com/absmach/magistrala/consumers/writers/api"
"github.com/absmach/magistrala/consumers/writers/cassandra"
cassandraclient "github.com/absmach/magistrala/internal/clients/cassandra"
mglog "github.com/absmach/magistrala/logger"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"github.com/gocql/gocql"
"golang.org/x/sync/errgroup"
)
const (
svcName = "cassandra-writer"
envPrefixDB = "MG_CASSANDRA_"
envPrefixHTTP = "MG_CASSANDRA_WRITER_HTTP_"
defSvcHTTPPort = "9004"
)
type config struct {
LogLevel string `env:"MG_CASSANDRA_WRITER_LOG_LEVEL" envDefault:"info"`
ConfigPath string `env:"MG_CASSANDRA_WRITER_CONFIG_PATH" envDefault:"/config.toml"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_CASSANDRA_WRITER_INSTANCE_ID" envDefault:""`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
// Create new cassandra writer service configurations
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
// Create new to cassandra client
csdSession, err := cassandraclient.SetupDB(envPrefixDB, cassandra.Table)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer csdSession.Close()
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
// Create new cassandra-writer repo
repo := newService(csdSession, logger)
repo = consumertracing.NewBlocking(tracer, repo, httpServerConfig)
// Create new pub sub broker
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
// Start new consumer
if err := consumers.Start(ctx, svcName, pubSub, repo, cfg.ConfigPath, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to create Cassandra writer: %s", err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
// Start servers
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("Cassandra writer service terminated: %s", err))
}
}
func newService(session *gocql.Session, logger *slog.Logger) consumers.BlockingConsumer {
repo := cassandra.New(session)
repo = api.LoggingMiddleware(repo, logger)
counter, latency := prometheus.MakeMetrics("cassandra", "message_writer")
repo = api.MetricsMiddleware(repo, counter, latency)
return repo
}
-161
View File
@@ -1,161 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains influxdb-reader main function to start the influxdb-reader service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
influxdbclient "github.com/absmach/magistrala/internal/clients/influxdb"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/readers"
"github.com/absmach/magistrala/readers/api"
"github.com/absmach/magistrala/readers/influxdb"
"github.com/caarlos0/env/v10"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"golang.org/x/sync/errgroup"
)
const (
svcName = "influxdb-reader"
envPrefixHTTP = "MG_INFLUX_READER_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
envPrefixAuthz = "MG_THINGS_AUTH_GRPC_"
envPrefixDB = "MG_INFLUXDB_"
defSvcHTTPPort = "9005"
)
type config struct {
LogLevel string `env:"MG_INFLUX_READER_LOG_LEVEL" envDefault:"info"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_INFLUX_READER_INSTANCE_ID" envDefault:""`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
ac, acHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer acHandler.Close()
logger.Info("Successfully connected to auth grpc server " + acHandler.Secure())
authConfig = auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuthz}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
tc, tcHandler, err := auth.SetupAuthz(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer tcHandler.Close()
logger.Info("Successfully connected to things grpc server " + tcHandler.Secure())
influxDBConfig := influxdbclient.Config{}
if err := env.ParseWithOptions(&influxDBConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(fmt.Sprintf("failed to load InfluxDB client configuration from environment variable : %s", err))
exitCode = 1
return
}
influxDBConfig.DBUrl = fmt.Sprintf("%s://%s:%s", influxDBConfig.Protocol, influxDBConfig.Host, influxDBConfig.Port)
repocfg := influxdb.RepoConfig{
Bucket: influxDBConfig.Bucket,
Org: influxDBConfig.Org,
}
client, err := influxdbclient.Connect(ctx, influxDBConfig)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to InfluxDB : %s", err))
exitCode = 1
return
}
defer client.Close()
repo := newService(client, repocfg, logger)
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, ac, tc, svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("InfluxDB reader service terminated: %s", err))
}
}
func newService(client influxdb2.Client, repocfg influxdb.RepoConfig, logger *slog.Logger) readers.MessageRepository {
repo := influxdb.New(client, repocfg)
repo = api.LoggingMiddleware(repo, logger)
counter, latency := prometheus.MakeMetrics("influxdb", "message_reader")
repo = api.MetricsMiddleware(repo, counter, latency)
return repo
}
-161
View File
@@ -1,161 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains influxdb-writer main function to start the influxdb-writer service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/consumers"
consumertracing "github.com/absmach/magistrala/consumers/tracing"
"github.com/absmach/magistrala/consumers/writers/api"
"github.com/absmach/magistrala/consumers/writers/influxdb"
influxdbclient "github.com/absmach/magistrala/internal/clients/influxdb"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"golang.org/x/sync/errgroup"
)
const (
svcName = "influxdb-writer"
envPrefixHTTP = "MG_INFLUX_WRITER_HTTP_"
envPrefixDB = "MG_INFLUXDB_"
defSvcHTTPPort = "9006"
)
type config struct {
LogLevel string `env:"MG_INFLUX_WRITER_LOG_LEVEL" envDefault:"info"`
ConfigPath string `env:"MG_INFLUX_WRITER_CONFIG_PATH" envDefault:"/config.toml"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_INFLUX_WRITER_INSTANCE_ID" envDefault:""`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
influxDBConfig := influxdbclient.Config{}
if err := env.ParseWithOptions(&influxDBConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(fmt.Sprintf("failed to load InfluxDB client configuration from environment variable : %s", err))
exitCode = 1
return
}
influxDBConfig.DBUrl = fmt.Sprintf("%s://%s:%s", influxDBConfig.Protocol, influxDBConfig.Host, influxDBConfig.Port)
repocfg := influxdb.RepoConfig{
Bucket: influxDBConfig.Bucket,
Org: influxDBConfig.Org,
}
client, err := influxdbclient.Connect(ctx, influxDBConfig)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to InfluxDB : %s", err))
exitCode = 1
return
}
defer client.Close()
repo := influxdb.NewAsync(client, repocfg)
repo = consumertracing.NewAsync(tracer, repo, httpServerConfig)
// Start consuming and logging errors.
go func(log *slog.Logger) {
for err := range repo.Errors() {
if err != nil {
log.Error(err.Error())
}
}
}(logger)
if err := consumers.Start(ctx, svcName, pubSub, repo, cfg.ConfigPath, logger); err != nil {
logger.Error(fmt.Sprintf("failed to start InfluxDB writer: %s", err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("InfluxDB reader service terminated: %s", err))
}
}
-233
View File
@@ -1,233 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains lora main function to start the lora service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
"time"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
redisclient "github.com/absmach/magistrala/internal/clients/redis"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/lora"
"github.com/absmach/magistrala/lora/api"
loraevents "github.com/absmach/magistrala/lora/events"
"github.com/absmach/magistrala/lora/mqtt"
"github.com/absmach/magistrala/pkg/events"
"github.com/absmach/magistrala/pkg/events/store"
"github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
mqttpaho "github.com/eclipse/paho.mqtt.golang"
"github.com/go-redis/redis/v8"
"golang.org/x/sync/errgroup"
)
const (
svcName = "lora-adapter"
envPrefixHTTP = "MG_LORA_ADAPTER_HTTP_"
defSvcHTTPPort = "9017"
thingsRMPrefix = "thing"
channelsRMPrefix = "channel"
connsRMPrefix = "connection"
thingsStream = "events.magistrala.things"
)
type config struct {
LogLevel string `env:"MG_LORA_ADAPTER_LOG_LEVEL" envDefault:"info"`
LoraMsgURL string `env:"MG_LORA_ADAPTER_MESSAGES_URL" envDefault:"tcp://localhost:1883"`
LoraMsgUser string `env:"MG_LORA_ADAPTER_MESSAGES_USER" envDefault:""`
LoraMsgPass string `env:"MG_LORA_ADAPTER_MESSAGES_PASS" envDefault:""`
LoraMsgTopic string `env:"MG_LORA_ADAPTER_MESSAGES_TOPIC" envDefault:"application/+/device/+/event/up"`
LoraMsgTimeout time.Duration `env:"MG_LORA_ADAPTER_MESSAGES_TIMEOUT" envDefault:"30s"`
ESConsumerName string `env:"MG_LORA_ADAPTER_EVENT_CONSUMER" envDefault:"lora-adapter"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_LORA_ADAPTER_INSTANCE_ID" envDefault:""`
ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"`
RouteMapURL string `env:"MG_LORA_ADAPTER_ROUTE_MAP_URL" envDefault:"redis://localhost:6379/0"`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
rmConn, err := redisclient.Connect(cfg.RouteMapURL)
if err != nil {
logger.Error(fmt.Sprintf("failed to setup route map redis client : %s", err))
exitCode = 1
return
}
defer rmConn.Close()
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pub, err := brokers.NewPublisher(ctx, cfg.BrokerURL)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pub.Close()
pub = brokerstracing.NewPublisher(httpServerConfig, tracer, pub)
svc := newService(pub, rmConn, thingsRMPrefix, channelsRMPrefix, connsRMPrefix, logger)
mqttConn, err := connectToMQTTBroker(cfg.LoraMsgURL, cfg.LoraMsgUser, cfg.LoraMsgPass, cfg.LoraMsgTimeout, logger)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
if err = subscribeToLoRaBroker(svc, mqttConn, cfg.LoraMsgTimeout, cfg.LoraMsgTopic, logger); err != nil {
logger.Error(fmt.Sprintf("failed to subscribe to Lora MQTT broker: %s", err))
exitCode = 1
return
}
if err = subscribeToThingsES(ctx, svc, cfg, logger); err != nil {
logger.Error(fmt.Sprintf("failed to subscribe to things event store: %s", err))
exitCode = 1
return
}
logger.Info("Subscribed to Event Store")
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("LoRa adapter terminated: %s", err))
}
}
func connectToMQTTBroker(burl, user, password string, timeout time.Duration, logger *slog.Logger) (mqttpaho.Client, error) {
opts := mqttpaho.NewClientOptions()
opts.AddBroker(burl)
opts.SetUsername(user)
opts.SetPassword(password)
opts.SetOnConnectHandler(func(_ mqttpaho.Client) {
logger.Info("Connected to Lora MQTT broker")
})
opts.SetConnectionLostHandler(func(_ mqttpaho.Client, err error) {
logger.Error(fmt.Sprintf("MQTT connection lost: %s", err))
})
client := mqttpaho.NewClient(opts)
if token := client.Connect(); token.WaitTimeout(timeout) && token.Error() != nil {
return nil, fmt.Errorf("failed to connect to Lora MQTT broker: %s", token.Error())
}
return client, nil
}
func subscribeToLoRaBroker(svc lora.Service, mc mqttpaho.Client, timeout time.Duration, topic string, logger *slog.Logger) error {
mqttBroker := mqtt.NewBroker(svc, mc, timeout, logger)
logger.Info("Subscribed to Lora MQTT broker")
if err := mqttBroker.Subscribe(topic); err != nil {
return fmt.Errorf("failed to subscribe to Lora MQTT broker: %s", err)
}
return nil
}
func subscribeToThingsES(ctx context.Context, svc lora.Service, cfg config, logger *slog.Logger) error {
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger)
if err != nil {
return err
}
subConfig := events.SubscriberConfig{
Stream: thingsStream,
Consumer: cfg.ESConsumerName,
Handler: loraevents.NewEventHandler(svc),
}
return subscriber.Subscribe(ctx, subConfig)
}
func newRouteMapRepository(client *redis.Client, prefix string, logger *slog.Logger) lora.RouteMapRepository {
logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix))
return loraevents.NewRouteMapRepository(client, prefix)
}
func newService(pub messaging.Publisher, rmConn *redis.Client, thingsRMPrefix, channelsRMPrefix, connsRMPrefix string, logger *slog.Logger) lora.Service {
thingsRM := newRouteMapRepository(rmConn, thingsRMPrefix, logger)
chansRM := newRouteMapRepository(rmConn, channelsRMPrefix, logger)
connsRM := newRouteMapRepository(rmConn, connsRMPrefix, logger)
svc := lora.New(pub, thingsRM, chansRM, connsRM)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics("lora_adapter", "api")
svc = api.MetricsMiddleware(svc, counter, latency)
return svc
}
-147
View File
@@ -1,147 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains mongodb-reader main function to start the mongodb-reader service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
mongoclient "github.com/absmach/magistrala/internal/clients/mongo"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/readers"
"github.com/absmach/magistrala/readers/api"
"github.com/absmach/magistrala/readers/mongodb"
"github.com/caarlos0/env/v10"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/sync/errgroup"
)
const (
svcName = "mongodb-reader"
envPrefixDB = "MG_MONGO_"
envPrefixHTTP = "MG_MONGO_READER_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
envPrefixAuthz = "MG_THINGS_AUTH_GRPC_"
defSvcHTTPPort = "9007"
)
type config struct {
LogLevel string `env:"MG_MONGO_READER_LOG_LEVEL" envDefault:"info"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_MONGO_READER_INSTANCE_ID" envDefault:""`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
db, err := mongoclient.Setup(envPrefixDB)
if err != nil {
logger.Error(fmt.Sprintf("failed to setup mongo database : %s", err))
exitCode = 1
return
}
repo := newService(db, logger)
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
ac, acHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer acHandler.Close()
logger.Info("Successfully connected to auth grpc server " + acHandler.Secure())
authConfig = auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuthz}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
tc, tcHandler, err := auth.SetupAuthz(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer tcHandler.Close()
logger.Info("Successfully connected to things grpc server " + tcHandler.Secure())
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, ac, tc, svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("MongoDB reader service terminated: %s", err))
}
}
func newService(db *mongo.Database, logger *slog.Logger) readers.MessageRepository {
repo := mongodb.New(db)
repo = api.LoggingMiddleware(repo, logger)
counter, latency := prometheus.MakeMetrics("mongodb", "message_reader")
repo = api.MetricsMiddleware(repo, counter, latency)
return repo
}
-148
View File
@@ -1,148 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains mongodb-writer main function to start the mongodb-writer service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/consumers"
consumertracing "github.com/absmach/magistrala/consumers/tracing"
"github.com/absmach/magistrala/consumers/writers/api"
"github.com/absmach/magistrala/consumers/writers/mongodb"
mongoclient "github.com/absmach/magistrala/internal/clients/mongo"
mglog "github.com/absmach/magistrala/logger"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/sync/errgroup"
)
const (
svcName = "mongodb-writer"
envPrefixDB = "MG_MONGO_"
envPrefixHTTP = "MG_MONGO_WRITER_HTTP_"
defSvcHTTPPort = "9008"
)
type config struct {
LogLevel string `env:"MG_MONGO_WRITER_LOG_LEVEL" envDefault:"info"`
ConfigPath string `env:"MG_MONGO_WRITER_CONFIG_PATH" envDefault:"/config.toml"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_MONGO_WRITER_INSTANCE_ID" envDefault:""`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
db, err := mongoclient.Setup(envPrefixDB)
if err != nil {
logger.Error(fmt.Sprintf("failed to setup mongo database : %s", err))
exitCode = 1
return
}
repo := newService(db, logger)
repo = consumertracing.NewBlocking(tracer, repo, httpServerConfig)
if err := consumers.Start(ctx, svcName, pubSub, repo, cfg.ConfigPath, logger); err != nil {
logger.Error(fmt.Sprintf("failed to start MongoDB writer: %s", err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svcName, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("MongoDB writer service terminated: %s", err))
}
}
func newService(db *mongo.Database, logger *slog.Logger) consumers.BlockingConsumer {
repo := mongodb.New(db)
repo = api.LoggingMiddleware(repo, logger)
counter, latency := prometheus.MakeMetrics("mongodb", "message_writer")
repo = api.MetricsMiddleware(repo, counter, latency)
return repo
}
-212
View File
@@ -1,212 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains opcua-adapter main function to start the opcua-adapter service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
redisclient "github.com/absmach/magistrala/internal/clients/redis"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/opcua"
"github.com/absmach/magistrala/opcua/api"
"github.com/absmach/magistrala/opcua/db"
opcuaevents "github.com/absmach/magistrala/opcua/events"
"github.com/absmach/magistrala/opcua/gopcua"
"github.com/absmach/magistrala/pkg/events"
"github.com/absmach/magistrala/pkg/events/store"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"github.com/go-redis/redis/v8"
"golang.org/x/sync/errgroup"
)
const (
svcName = "opc-ua-adapter"
envPrefixHTTP = "MG_OPCUA_ADAPTER_HTTP_"
defSvcHTTPPort = "8180"
thingsRMPrefix = "thing"
channelsRMPrefix = "channel"
connectionRMPrefix = "connection"
thingsStream = "events.magistrala.things"
)
type config struct {
LogLevel string `env:"MG_OPCUA_ADAPTER_LOG_LEVEL" envDefault:"info"`
ESConsumerName string `env:"MG_OPCUA_ADAPTER_EVENT_CONSUMER" envDefault:"opcua-adapter"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_OPCUA_ADAPTER_INSTANCE_ID" envDefault:""`
ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"`
RouteMapURL string `env:"MG_OPCUA_ADAPTER_ROUTE_MAP_URL" envDefault:"redis://localhost:6379/0"`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, httpCancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
opcConfig := opcua.Config{}
if err := env.Parse(&opcConfig); err != nil {
log.Fatalf("failed to load %s opcua client configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
rmConn, err := redisclient.Connect(cfg.RouteMapURL)
if err != nil {
logger.Error(fmt.Sprintf("failed to setup %s bootstrap event store redis client : %s", svcName, err))
exitCode = 1
return
}
defer rmConn.Close()
thingRM := newRouteMapRepositoy(rmConn, thingsRMPrefix, logger)
chanRM := newRouteMapRepositoy(rmConn, channelsRMPrefix, logger)
connRM := newRouteMapRepositoy(rmConn, connectionRMPrefix, logger)
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
sub := gopcua.NewSubscriber(ctx, pubSub, thingRM, chanRM, connRM, logger)
browser := gopcua.NewBrowser(ctx, logger)
svc := newService(sub, browser, thingRM, chanRM, connRM, opcConfig, logger)
go subscribeToStoredSubs(ctx, sub, opcConfig, logger)
if err = subscribeToThingsES(ctx, svc, cfg, logger); err != nil {
logger.Error(fmt.Sprintf("failed to subscribe to things event store: %s", err))
exitCode = 1
return
}
logger.Info("Subscribed to Event Store")
hs := httpserver.NewServer(ctx, httpCancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, httpCancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, httpCancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("OPC-UA adapter service terminated: %s", err))
}
}
func subscribeToStoredSubs(ctx context.Context, sub opcua.Subscriber, cfg opcua.Config, logger *slog.Logger) {
// Get all stored subscriptions
nodes, err := db.ReadAll()
if err != nil {
logger.Warn(fmt.Sprintf("Read stored subscriptions failed: %s", err))
}
for _, n := range nodes {
cfg.ServerURI = n.ServerURI
cfg.NodeID = n.NodeID
go func() {
if err := sub.Subscribe(ctx, cfg); err != nil {
logger.Warn(fmt.Sprintf("Subscription failed: %s", err))
}
}()
}
}
func subscribeToThingsES(ctx context.Context, svc opcua.Service, cfg config, logger *slog.Logger) error {
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger)
if err != nil {
return err
}
subConfig := events.SubscriberConfig{
Stream: thingsStream,
Consumer: cfg.ESConsumerName,
Handler: opcuaevents.NewEventHandler(svc),
}
return subscriber.Subscribe(ctx, subConfig)
}
func newRouteMapRepositoy(client *redis.Client, prefix string, logger *slog.Logger) opcua.RouteMapRepository {
logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix))
return opcuaevents.NewRouteMapRepository(client, prefix)
}
func newService(sub opcua.Subscriber, browser opcua.Browser, thingRM, chanRM, connRM opcua.RouteMapRepository, opcuaConfig opcua.Config, logger *slog.Logger) opcua.Service {
svc := opcua.New(sub, browser, thingRM, chanRM, connRM, opcuaConfig, logger)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics("opc_ua_adapter", "api")
svc = api.MetricsMiddleware(svc, counter, latency)
return svc
}
-189
View File
@@ -1,189 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains smpp-notifier main function to start the smpp-notifier service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/consumers"
"github.com/absmach/magistrala/consumers/notifiers"
"github.com/absmach/magistrala/consumers/notifiers/api"
notifierpg "github.com/absmach/magistrala/consumers/notifiers/postgres"
mgsmpp "github.com/absmach/magistrala/consumers/notifiers/smpp"
"github.com/absmach/magistrala/consumers/notifiers/tracing"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
pgclient "github.com/absmach/magistrala/pkg/postgres"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/ulid"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"github.com/jmoiron/sqlx"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
const (
svcName = "smpp-notifier"
envPrefixDB = "MG_SMPP_NOTIFIER_DB_"
envPrefixHTTP = "MG_SMPP_NOTIFIER_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
defDB = "subscriptions"
defSvcHTTPPort = "9014"
)
type config struct {
LogLevel string `env:"MG_SMPP_NOTIFIER_LOG_LEVEL" envDefault:"info"`
From string `env:"MG_SMPP_NOTIFIER_FROM_ADDR" envDefault:""`
ConfigPath string `env:"MG_SMPP_NOTIFIER_CONFIG_PATH" envDefault:"/config.toml"`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_SMPP_NOTIFIER_INSTANCE_ID" envDefault:""`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
dbConfig := pgclient.Config{Name: defDB}
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s Postgres configuration : %s", svcName, err))
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *notifierpg.Migration())
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer db.Close()
smppConfig := mgsmpp.Config{}
if err := env.Parse(&smppConfig); err != nil {
logger.Error(fmt.Sprintf("failed to load SMPP configuration from environment : %s", err))
exitCode = 1
return
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
authClient, authHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authHandler.Close()
logger.Info("Successfully connected to auth grpc server " + authHandler.Secure())
svc := newService(db, tracer, authClient, cfg, smppConfig, logger)
if err = consumers.Start(ctx, svcName, pubSub, svc, cfg.ConfigPath, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create Postgres writer: %s", err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("SMPP notifier service terminated: %s", err))
}
}
func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, sc mgsmpp.Config, logger *slog.Logger) notifiers.Service {
database := notifierpg.NewDatabase(db, tracer)
repo := tracing.New(tracer, notifierpg.New(database))
idp := ulid.New()
notifier := mgsmpp.New(sc)
svc := notifiers.New(authClient, repo, idp, notifier, c.From)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics("notifier", "smpp")
svc = api.MetricsMiddleware(svc, counter, latency)
return svc
}
-203
View File
@@ -1,203 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains smtp-notifier main function to start the smtp-notifier service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/consumers"
"github.com/absmach/magistrala/consumers/notifiers"
"github.com/absmach/magistrala/consumers/notifiers/api"
notifierpg "github.com/absmach/magistrala/consumers/notifiers/postgres"
"github.com/absmach/magistrala/consumers/notifiers/smtp"
"github.com/absmach/magistrala/consumers/notifiers/tracing"
"github.com/absmach/magistrala/internal/email"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
pgclient "github.com/absmach/magistrala/pkg/postgres"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/ulid"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v10"
"github.com/jmoiron/sqlx"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
const (
svcName = "smtp-notifier"
envPrefixDB = "MG_SMTP_NOTIFIER_DB_"
envPrefixHTTP = "MG_SMTP_NOTIFIER_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
defDB = "subscriptions"
defSvcHTTPPort = "9015"
)
type config struct {
LogLevel string `env:"MG_SMTP_NOTIFIER_LOG_LEVEL" envDefault:"info"`
ConfigPath string `env:"MG_SMTP_NOTIFIER_CONFIG_PATH" envDefault:"/config.toml"`
From string `env:"MG_SMTP_NOTIFIER_FROM_ADDR" envDefault:""`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_SMTP_NOTIFIER_INSTANCE_ID" envDefault:""`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
dbConfig := pgclient.Config{Name: defDB}
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s Postgres configuration : %s", svcName, err))
exitCode = 1
return
}
db, err := pgclient.Setup(dbConfig, *notifierpg.Migration())
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer db.Close()
ec := email.Config{}
if err := env.Parse(&ec); err != nil {
logger.Error(fmt.Sprintf("failed to load email configuration : %s", err))
exitCode = 1
return
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
authClient, authHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authHandler.Close()
logger.Info("Successfully connected to auth grpc server " + authHandler.Secure())
svc, err := newService(db, tracer, authClient, cfg, ec, logger)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
if err = consumers.Start(ctx, svcName, pubSub, svc, cfg.ConfigPath, logger); err != nil {
logger.Error(fmt.Sprintf("failed to create Postgres writer: %s", err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("SMTP notifier service terminated: %s", err))
}
}
func newService(db *sqlx.DB, tracer trace.Tracer, authClient magistrala.AuthServiceClient, c config, ec email.Config, logger *slog.Logger) (notifiers.Service, error) {
database := notifierpg.NewDatabase(db, tracer)
repo := tracing.New(tracer, notifierpg.New(database))
idp := ulid.New()
agent, err := email.New(&ec)
if err != nil {
return nil, fmt.Errorf("failed to create email agent: %s", err)
}
notifier := smtp.New(agent)
svc := notifiers.New(authClient, repo, idp, notifier, c.From)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics("notifier", "smtp")
svc = api.MetricsMiddleware(svc, counter, latency)
return svc, nil
}
-242
View File
@@ -1,242 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package main contains twins main function to start the twins service.
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"
chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/magistrala"
mongoclient "github.com/absmach/magistrala/internal/clients/mongo"
redisclient "github.com/absmach/magistrala/internal/clients/redis"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/auth"
jaegerclient "github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/messaging"
"github.com/absmach/magistrala/pkg/messaging/brokers"
brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/server"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
localusers "github.com/absmach/magistrala/things/standalone"
"github.com/absmach/magistrala/twins"
"github.com/absmach/magistrala/twins/api"
twapi "github.com/absmach/magistrala/twins/api/http"
"github.com/absmach/magistrala/twins/events"
twmongodb "github.com/absmach/magistrala/twins/mongodb"
"github.com/absmach/magistrala/twins/tracing"
"github.com/caarlos0/env/v10"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/mongo"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
const (
svcName = "twins"
envPrefixDB = "MG_TWINS_DB_"
envPrefixHTTP = "MG_TWINS_HTTP_"
envPrefixAuth = "MG_AUTH_GRPC_"
defSvcHTTPPort = "9018"
)
type config struct {
LogLevel string `env:"MG_TWINS_LOG_LEVEL" envDefault:"info"`
StandaloneID string `env:"MG_TWINS_STANDALONE_ID" envDefault:""`
StandaloneToken string `env:"MG_TWINS_STANDALONE_TOKEN" envDefault:""`
ChannelID string `env:"MG_TWINS_CHANNEL_ID" envDefault:""`
BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://jaeger:14268/api/traces"`
SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MG_TWINS_INSTANCE_ID" envDefault:""`
ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"`
CacheURL string `env:"MG_TWINS_CACHE_URL" envDefault:"redis://localhost:6379/0"`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err.Error())
}
var exitCode int
defer mglog.ExitWithError(&exitCode)
if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
exitCode = 1
return
}
}
httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}
cacheClient, err := redisclient.Connect(cfg.CacheURL)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer cacheClient.Close()
db, err := mongoclient.Setup(envPrefixDB)
if err != nil {
logger.Error(fmt.Sprintf("failed to setup postgres database : %s", err))
exitCode = 1
return
}
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
exitCode = 1
return
}
defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)
var authClient magistrala.AuthServiceClient
switch cfg.StandaloneID != "" && cfg.StandaloneToken != "" {
case true:
authClient = localusers.NewAuthService(cfg.StandaloneID, cfg.StandaloneToken)
default:
authConfig := auth.Config{}
if err := env.ParseWithOptions(&authConfig, env.Options{Prefix: envPrefixAuth}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err))
exitCode = 1
return
}
authServiceClient, authHandler, err := auth.Setup(ctx, authConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer authHandler.Close()
authClient = authServiceClient
logger.Info("Successfully connected to auth grpc server " + authHandler.Secure())
}
pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer pubSub.Close()
pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub)
svc, err := newService(ctx, svcName, pubSub, cfg, authClient, tracer, db, cacheClient, logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err))
exitCode = 1
return
}
hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, twapi.MakeHandler(svc, logger, cfg.InstanceID), logger)
if cfg.SendTelemetry {
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
go chc.CallHome(ctx)
}
g.Go(func() error {
return hs.Start()
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("Twins service terminated: %s", err))
}
}
func newService(ctx context.Context, id string, ps messaging.PubSub, cfg config, users magistrala.AuthServiceClient, tracer trace.Tracer, db *mongo.Database, cacheclient *redis.Client, logger *slog.Logger) (twins.Service, error) {
twinRepo := twmongodb.NewTwinRepository(db)
twinRepo = tracing.TwinRepositoryMiddleware(tracer, twinRepo)
stateRepo := twmongodb.NewStateRepository(db)
stateRepo = tracing.StateRepositoryMiddleware(tracer, stateRepo)
idProvider := uuid.New()
twinCache := events.NewTwinCache(cacheclient)
twinCache = tracing.TwinCacheMiddleware(tracer, twinCache)
svc := twins.New(ps, users, twinRepo, twinCache, stateRepo, idProvider, cfg.ChannelID, logger)
var err error
svc, err = events.NewEventStoreMiddleware(ctx, svc, cfg.ESURL)
if err != nil {
return nil, err
}
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics(svcName, "api")
svc = api.MetricsMiddleware(svc, counter, latency)
subCfg := messaging.SubscriberConfig{
ID: id,
Topic: brokers.SubjectAllChannels,
Handler: handle(ctx, logger, cfg.ChannelID, svc),
}
if err = ps.Subscribe(ctx, subCfg); err != nil {
logger.Error(err.Error())
}
return svc, nil
}
func handle(ctx context.Context, logger *slog.Logger, chanID string, svc twins.Service) handlerFunc {
return func(msg *messaging.Message) error {
if msg.GetChannel() == chanID {
return nil
}
if err := svc.SaveStates(ctx, msg); err != nil {
logger.Error(fmt.Sprintf("State save failed: %s", err))
return err
}
return nil
}
}
type handlerFunc func(msg *messaging.Message) error
func (h handlerFunc) Handle(msg *messaging.Message) error {
return h(msg)
}
func (h handlerFunc) Cancel() error {
return nil
}
-51
View File
@@ -1,51 +0,0 @@
# SMPP Notifier
SMPP Notifier implements notifier for send SMS notifications.
## Configuration
The Subscription service using SMPP Notifier 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 |
| --------------------------------- | --------------------------------------------------------------------------------- | ------------------------------ |
| MG_SMPP_NOTIFIER_LOG_LEVEL | Log level for SMPP Notifier (debug, info, warn, error) | info |
| MG_SMPP_NOTIFIER_FROM_ADDRESS | From address for SMS notifications | |
| MG_SMPP_NOTIFIER_CONFIG_PATH | Config file path with Message broker subjects list, payload type and content-type | /config.toml |
| MG_SMPP_NOTIFIER_HTTP_HOST | Service HTTP host | localhost |
| MG_SMPP_NOTIFIER_HTTP_PORT | Service HTTP port | 9014 |
| MG_SMPP_NOTIFIER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" |
| MG_SMPP_NOTIFIER_HTTP_SERVER_KEY | Service HTTP server key | "" |
| MG_SMPP_NOTIFIER_DB_HOST | Database host address | localhost |
| MG_SMPP_NOTIFIER_DB_PORT | Database host port | 5432 |
| MG_SMPP_NOTIFIER_DB_USER | Database user | magistrala |
| MG_SMPP_NOTIFIER_DB_PASS | Database password | magistrala |
| MG_SMPP_NOTIFIER_DB_NAME | Name of the database used by the service | subscriptions |
| MG_SMPP_NOTIFIER_DB_SSL_MODE | DB connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MG_SMPP_NOTIFIER_DB_SSL_CERT | Path to the PEM encoded certificate file | "" |
| MG_SMPP_NOTIFIER_DB_SSL_KEY | Path to the PEM encoded key file | "" |
| MG_SMPP_NOTIFIER_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" |
| MG_SMPP_ADDRESS | SMPP address [host:port] | |
| MG_SMPP_USERNAME | SMPP Username | |
| MG_SMPP_PASSWORD | SMPP Password | |
| MG_SMPP_SYSTEM_TYPE | SMPP System Type | |
| MG_SMPP_SRC_ADDR_TON | SMPP source address TON | |
| MG_SMPP_DST_ADDR_TON | SMPP destination address TON | |
| MG_SMPP_SRC_ADDR_NPI | SMPP source address NPI | |
| MG_SMPP_DST_ADDR_NPI | SMPP destination address NPI | |
| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 |
| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
| MG_AUTH_GRPC_CLIENT_TLS | Auth client TLS flag | false |
| MG_AUTH_GRPC_CA_CERT | Path to Auth client CA certs in pem format | "" |
| MG_MESSAGE_BROKER_URL | Message broker URL | nats://127.0.0.1:4222 |
| MG_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_SMPP_NOTIFIER_INSTANCE_ID | SMPP Notifier instance ID | "" |
## Usage
Starting service will start consuming messages and sending SMS when a message is received.
[doc]: https://docs.magistrala.abstractmachines.fr
-21
View File
@@ -1,21 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package smpp
import (
"crypto/tls"
)
// Config represents SMPP transmitter configuration.
type Config struct {
Address string `env:"MG_SMPP_ADDRESS" envDefault:""`
Username string `env:"MG_SMPP_USERNAME" envDefault:""`
Password string `env:"MG_SMPP_PASSWORD" envDefault:""`
SystemType string `env:"MG_SMPP_SYSTEM_TYPE" envDefault:""`
SourceAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_TON" envDefault:"0"`
SourceAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_TON" envDefault:"0"`
DestAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_NPI" envDefault:"0"`
DestAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_NPI" envDefault:"0"`
TLS *tls.Config
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package smpp contains the domain concept definitions needed to
// support Magistrala SMS notifications.
package smpp
-67
View File
@@ -1,67 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package smpp
import (
"time"
"github.com/absmach/magistrala/consumers/notifiers"
"github.com/absmach/magistrala/pkg/messaging"
"github.com/absmach/magistrala/pkg/transformers"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/fiorix/go-smpp/smpp"
"github.com/fiorix/go-smpp/smpp/pdu/pdufield"
"github.com/fiorix/go-smpp/smpp/pdu/pdutext"
)
var _ notifiers.Notifier = (*notifier)(nil)
type notifier struct {
transmitter *smpp.Transmitter
transformer transformers.Transformer
sourceAddrTON uint8
sourceAddrNPI uint8
destAddrTON uint8
destAddrNPI uint8
}
// New instantiates SMTP message notifier.
func New(cfg Config) notifiers.Notifier {
t := &smpp.Transmitter{
Addr: cfg.Address,
User: cfg.Username,
Passwd: cfg.Password,
SystemType: cfg.SystemType,
RespTimeout: 3 * time.Second,
}
t.Bind()
ret := &notifier{
transmitter: t,
transformer: json.New([]json.TimeField{}),
sourceAddrTON: cfg.SourceAddrTON,
destAddrTON: cfg.DestAddrTON,
sourceAddrNPI: cfg.SourceAddrNPI,
destAddrNPI: cfg.DestAddrNPI,
}
return ret
}
func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error {
send := &smpp.ShortMessage{
Src: from,
DstList: to,
Validity: 10 * time.Minute,
SourceAddrTON: n.sourceAddrTON,
DestAddrTON: n.destAddrTON,
SourceAddrNPI: n.sourceAddrNPI,
DestAddrNPI: n.destAddrNPI,
Text: pdutext.Raw(msg.GetPayload()),
Register: pdufield.NoDeliveryReceipt,
}
_, err := n.transmitter.Submit(send)
if err != nil {
return err
}
return nil
}
-51
View File
@@ -1,51 +0,0 @@
# SMTP Notifier
SMTP Notifier implements notifier for send SMTP notifications.
## Configuration
The Subscription service using SMTP Notifier 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 |
| --------------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
| MG_SMTP_NOTIFIER_LOG_LEVEL | Log level for SMT Notifier (debug, info, warn, error) | info |
| MG_SMTP_NOTIFIER_FROM_ADDRESS | From address for SMTP notifications | |
| MG_SMTP_NOTIFIER_CONFIG_PATH | Path to the config file with message broker subjects configuration | disable |
| MG_SMTP_NOTIFIER_HTTP_HOST | SMTP Notifier service HTTP host | localhost |
| MG_SMTP_NOTIFIER_HTTP_PORT | SMTP Notifier service HTTP port | 9015 |
| MG_SMTP_NOTIFIER_HTTP_SERVER_CERT | SMTP Notifier service HTTP server certificate path | "" |
| MG_SMTP_NOTIFIER_HTTP_SERVER_KEY | SMTP Notifier service HTTP server key | "" |
| MG_SMTP_NOTIFIER_DB_HOST | Database host address | localhost |
| MG_SMTP_NOTIFIER_DB_PORT | Database host port | 5432 |
| MG_SMTP_NOTIFIER_DB_USER | Database user | magistrala |
| MG_SMTP_NOTIFIER_DB_PASS | Database password | magistrala |
| MG_SMTP_NOTIFIER_DB_NAME | Name of the database used by the service | subscriptions |
| MG_SMTP_NOTIFIER_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MG_SMTP_NOTIFIER_DB_SSL_CERT | Path to the PEM encoded cert file | "" |
| MG_SMTP_NOTIFIER_DB_SSL_KEY | Path to the PEM encoded certificate key | "" |
| MG_SMTP_NOTIFIER_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" |
| MG_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MG_MESSAGE_BROKER_URL | Message broker URL | nats://127.0.0.1:4222 |
| MG_EMAIL_HOST | Mail server host | localhost |
| MG_EMAIL_PORT | Mail server port | 25 |
| MG_EMAIL_USERNAME | Mail server username | |
| MG_EMAIL_PASSWORD | Mail server password | |
| MG_EMAIL_FROM_ADDRESS | Email "from" address | |
| MG_EMAIL_FROM_NAME | Email "from" name | |
| MG_EMAIL_TEMPLATE | Email template for sending notification emails | email.tmpl |
| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 |
| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
| MG_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS flag | false |
| MG_AUTH_GRPC_CA_CERT | Path to Auth service CA cert in pem format | "" |
| MG_AUTH_CLIENT_TLS | Auth client TLS flag | false |
| MG_AUTH_CA_CERTS | Path to Auth client CA certs in pem format | "" |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_SMTP_NOTIFIER_INSTANCE_ID | SMTP Notifier instance ID | "" |
## Usage
Starting service will start consuming messages and sending emails when a message is received.
[doc]: https://docs.magistrala.abstractmachines.fr
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package smtp contains the domain concept definitions needed to
// support Magistrala SMTP notifications.
package smtp
-81
View File
@@ -1,81 +0,0 @@
# Cassandra writer
Cassandra writer provides message repository implementation for Cassandra.
## 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 |
| ------------------------------------ | ----------------------------------------------------------------------- | ------------------------------ |
| MG_CASSANDRA_WRITER_LOG_LEVEL | Log level for Cassandra writer (debug, info, warn, error) | info |
| MG_CASSANDRA_WRITER_CONFIG_PATH | Config file path with NATS subjects list, payload type and content-type | /config.toml |
| MG_CASSANDRA_WRITER_HTTP_HOST | Cassandra service HTTP host | |
| MG_CASSANDRA_WRITER_HTTP_PORT | Cassandra service HTTP port | 9004 |
| MG_CASSANDRA_WRITER_HTTP_SERVER_CERT | Cassandra service HTTP server certificate path | |
| MG_CASSANDRA_WRITER_HTTP_SERVER_KEY | Cassandra service HTTP server key path | |
| MG_CASSANDRA_CLUSTER | Cassandra cluster comma separated addresses | 127.0.0.1 |
| MG_CASSANDRA_KEYSPACE | Cassandra keyspace name | magistrala |
| MG_CASSANDRA_USER | Cassandra DB username | magistrala |
| MG_CASSANDRA_PASS | Cassandra DB password | magistrala |
| MG_CASSANDRA_PORT | Cassandra DB port | 9042 |
| MG_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 |
| MG_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_CASSANDRA_WRITER_INSTANCE_ID | Cassandra writer instance ID | |
## Deployment
The service itself is distributed as Docker container. Check the [`cassandra-writer`](https://github.com/absmach/magistrala/blob/main/docker/addons/cassandra-writer/docker-compose.yml#L30-L49) service section in docker-compose file to see how service is deployed.
To start the service, execute the following shell script:
```bash
# download the latest version of the service
git clone https://github.com/absmach/magistrala
cd magistrala
# compile the cassandra writer
make cassandra-writer
# copy binary to bin
make install
# Set the environment variables and run the service
MG_CASSANDRA_WRITER_LOG_LEVEL=[Cassandra writer log level] \
MG_CASSANDRA_WRITER_CONFIG_PATH=[Config file path with NATS subjects list, payload type and content-type] \
MG_CASSANDRA_WRITER_HTTP_HOST=[Cassandra service HTTP host] \
MG_CASSANDRA_WRITER_HTTP_PORT=[Cassandra service HTTP port] \
MG_CASSANDRA_WRITER_HTTP_SERVER_CERT=[Cassandra service HTTP server cert] \
MG_CASSANDRA_WRITER_HTTP_SERVER_KEY=[Cassandra service HTTP server key] \
MG_CASSANDRA_CLUSTER=[Cassandra cluster comma separated addresses] \
MG_CASSANDRA_KEYSPACE=[Cassandra keyspace name] \
MG_CASSANDRA_USER=[Cassandra DB username] \
MG_CASSANDRA_PASS=[Cassandra DB password] \
MG_CASSANDRA_PORT=[Cassandra DB port] \
MG_MESSAGE_BROKER_URL=[Message Broker instance URL] \
MG_JAEGER_URL=[Jaeger server URL] \
MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \
MG_CASSANDRA_WRITER_INSTANCE_ID=[Cassandra writer instance ID] \
$GOBIN/magistrala-cassandra-writer
```
### Using docker-compose
This service can be deployed using docker containers. Docker compose file is
available in `<project_root>/docker/addons/cassandra-writer/docker-compose.yml`.
In order to run all Magistrala core services, as well as mentioned optional ones,
execute following command:
```bash
./docker/addons/cassandra-writer/init.sh
```
## Usage
Starting service will start consuming normalized messages in SenML format.
[doc]: https://docs.magistrala.abstractmachines.fr
-102
View File
@@ -1,102 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cassandra
import (
"context"
"encoding/json"
"fmt"
"github.com/absmach/magistrala/consumers"
"github.com/absmach/magistrala/pkg/errors"
mgjson "github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
"github.com/gocql/gocql"
)
var (
errSaveMessage = errors.New("failed to save message to cassandra database")
errNoTable = errors.New("table does not exist")
)
var _ consumers.BlockingConsumer = (*cassandraRepository)(nil)
type cassandraRepository struct {
session *gocql.Session
}
// New instantiates Cassandra message repository.
func New(session *gocql.Session) consumers.BlockingConsumer {
return &cassandraRepository{session}
}
func (cr *cassandraRepository) ConsumeBlocking(_ context.Context, message interface{}) error {
switch m := message.(type) {
case mgjson.Messages:
return cr.saveJSON(m)
default:
return cr.saveSenml(m)
}
}
func (cr *cassandraRepository) saveSenml(messages interface{}) error {
msgs, ok := messages.([]senml.Message)
if !ok {
return errSaveMessage
}
cql := `INSERT INTO messages (id, channel, subtopic, publisher, protocol,
name, unit, value, string_value, bool_value, data_value, sum,
time, update_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
id := gocql.TimeUUID()
for _, msg := range msgs {
err := cr.session.Query(cql, id, msg.Channel, msg.Subtopic, msg.Publisher,
msg.Protocol, msg.Name, msg.Unit, msg.Value, msg.StringValue,
msg.BoolValue, msg.DataValue, msg.Sum, msg.Time, msg.UpdateTime).Exec()
if err != nil {
return errors.Wrap(errSaveMessage, err)
}
}
return nil
}
func (cr *cassandraRepository) saveJSON(msgs mgjson.Messages) error {
if err := cr.insertJSON(msgs); err != nil {
if err == errNoTable {
if err := cr.createTable(msgs.Format); err != nil {
return err
}
return cr.insertJSON(msgs)
}
return err
}
return nil
}
func (cr *cassandraRepository) insertJSON(msgs mgjson.Messages) error {
cql := `INSERT INTO %s (id, channel, created, subtopic, publisher, protocol, payload) VALUES (?, ?, ?, ?, ?, ?, ?)`
cql = fmt.Sprintf(cql, msgs.Format)
for _, msg := range msgs.Data {
pld, err := json.Marshal(msg.Payload)
if err != nil {
return err
}
id := gocql.TimeUUID()
err = cr.session.Query(cql, id, msg.Channel, msg.Created, msg.Subtopic, msg.Publisher, msg.Protocol, string(pld)).Exec()
if err != nil {
if err.Error() == fmt.Sprintf("unconfigured table %s", msgs.Format) {
return errNoTable
}
return errors.Wrap(errSaveMessage, err)
}
}
return nil
}
func (cr *cassandraRepository) createTable(name string) error {
q := fmt.Sprintf(jsonTable, name)
return cr.session.Query(q).Exec()
}
@@ -1,122 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cassandra_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/absmach/magistrala/consumers/writers/cassandra"
casclient "github.com/absmach/magistrala/internal/clients/cassandra"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
keyspace = "magistrala"
msgsNum = 42
valueFields = 5
subtopic = "topic"
)
var addr = "localhost"
var (
v float64 = 5
stringV = "value"
boolV = true
dataV = "base64"
sum float64 = 42
)
func TestSaveSenml(t *testing.T) {
session, err := casclient.Connect(casclient.Config{
Hosts: []string{addr},
Keyspace: keyspace,
})
require.Nil(t, err, fmt.Sprintf("failed to connect to Cassandra: %s", err))
err = casclient.InitDB(session, cassandra.Table)
require.Nil(t, err, fmt.Sprintf("failed to initialize to Cassandra: %s", err))
repo := cassandra.New(session)
now := time.Now().Unix()
msg := senml.Message{
Channel: "1",
Publisher: "1",
Protocol: "mqtt",
}
var msgs []senml.Message
for i := 0; i < msgsNum; i++ {
// Mix possible values as well as value sum.
count := i % valueFields
switch count {
case 0:
msg.Subtopic = subtopic
msg.Value = &v
case 1:
msg.BoolValue = &boolV
case 2:
msg.StringValue = &stringV
case 3:
msg.DataValue = &dataV
case 4:
msg.Sum = &sum
}
msg.Time = float64(now + int64(i))
msgs = append(msgs, msg)
}
err = repo.ConsumeBlocking(context.TODO(), msgs)
assert.Nil(t, err, fmt.Sprintf("expected no error, got %s", err))
}
func TestSaveJSON(t *testing.T) {
session, err := casclient.Connect(casclient.Config{
Hosts: []string{addr},
Keyspace: keyspace,
})
require.Nil(t, err, fmt.Sprintf("failed to connect to Cassandra: %s", err))
repo := cassandra.New(session)
chid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
pubid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
msg := json.Message{
Channel: chid.String(),
Publisher: pubid.String(),
Created: time.Now().Unix(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
},
}
now := time.Now().Unix()
msgs := json.Messages{
Format: "some_json",
}
for i := 0; i < msgsNum; i++ {
msg.Created = now + int64(i)
msgs.Data = append(msgs.Data, msg)
}
err = repo.ConsumeBlocking(context.TODO(), msgs)
assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err))
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package cassandra contains the domain concept definitions needed to
// support Magistrala Cassandra writer service.
package cassandra
-36
View File
@@ -1,36 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cassandra
const (
// Table contains query for default table created in cassandra db.
Table = `CREATE TABLE IF NOT EXISTS messages (
id uuid,
channel text,
subtopic text,
publisher text,
protocol text,
name text,
unit text,
value double,
string_value text,
bool_value boolean,
data_value blob,
sum double,
time double,
update_time double,
PRIMARY KEY (publisher, time, subtopic, name)
) WITH CLUSTERING ORDER BY (time DESC)`
jsonTable = `CREATE TABLE IF NOT EXISTS %s (
id uuid,
channel text,
subtopic text,
publisher text,
protocol text,
created bigint,
payload text,
PRIMARY KEY (publisher, created, subtopic)
) WITH CLUSTERING ORDER BY (created DESC)`
)
-83
View File
@@ -1,83 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cassandra_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/absmach/magistrala/internal/clients/cassandra"
mglog "github.com/absmach/magistrala/logger"
"github.com/gocql/gocql"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
)
var logger, _ = mglog.New(os.Stdout, "info")
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
logger.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
container, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "cassandra",
Tag: "3.11.16",
}, func(config *docker.HostConfig) {
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
port := container.GetPort("9042/tcp")
addr = fmt.Sprintf("%s:%s", addr, port)
if err = pool.Retry(func() error {
if err := createKeyspace([]string{addr}); err != nil {
return err
}
session, err := cassandra.Connect(cassandra.Config{
Hosts: []string{addr},
Keyspace: keyspace,
})
if err != nil {
return err
}
defer session.Close()
return nil
}); err != nil {
logger.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
code := m.Run()
if err := pool.Purge(container); err != nil {
logger.Error(fmt.Sprintf("Could not purge container: %s", err))
}
os.Exit(code)
}
func createKeyspace(hosts []string) error {
cluster := gocql.NewCluster(hosts...)
cluster.Consistency = gocql.Quorum
session, err := cluster.CreateSession()
if err != nil {
return err
}
defer session.Close()
keyspaceCQL := fmt.Sprintf(`CREATE KEYSPACE IF NOT EXISTS %s WITH replication =
{'class':'SimpleStrategy','replication_factor':'1'}`, keyspace)
return session.Query(keyspaceCQL).Exec()
}
-105
View File
@@ -1,105 +0,0 @@
# InfluxDB writer
InfluxDB writer provides message repository implementation for InfluxDB.
## 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 |
| --------------------------------- | --------------------------------------------------------------------------------- | ------------------------------ |
| MG_INFLUX_WRITER_LOG_LEVEL | Log level for InfluxDB writer (debug, info, warn, error) | info |
| MG_INFLUX_WRITER_CONFIG_PATH | Config file path with message broker subjects list, payload type and content-type | /configs.toml |
| MG_INFLUX_WRITER_HTTP_HOST | Service HTTP host | |
| MG_INFLUX_WRITER_HTTP_PORT | Service HTTP port | 9006 |
| MG_INFLUX_WRITER_HTTP_SERVER_CERT | Path to server certificate in pem format | |
| MG_INFLUX_WRITER_HTTP_SERVER_KEY | Path to server key in pem format | |
| MG_INFLUXDB_PROTOCOL | InfluxDB protocol | http |
| MG_INFLUXDB_HOST | InfluxDB host name | magistrala-influxdb |
| MG_INFLUXDB_PORT | Default port of InfluxDB database | 8086 |
| MG_INFLUXDB_ADMIN_USER | Default user of InfluxDB database | magistrala |
| MG_INFLUXDB_ADMIN_PASSWORD | Default password of InfluxDB user | magistrala |
| MG_INFLUXDB_NAME | InfluxDB database name | magistrala |
| MG_INFLUXDB_BUCKET | InfluxDB bucket name | magistrala-bucket |
| MG_INFLUXDB_ORG | InfluxDB organization name | magistrala |
| MG_INFLUXDB_TOKEN | InfluxDB API token | magistrala-token |
| MG_INFLUXDB_DBURL | InfluxDB database URL | |
| MG_INFLUXDB_USER_AGENT | InfluxDB user agent | |
| MG_INFLUXDB_TIMEOUT | InfluxDB client connection readiness timeout | 1s |
| MG_INFLUXDB_INSECURE_SKIP_VERIFY | InfluxDB client connection insecure skip verify | false |
| MG_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 |
| MG_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_INFLUX_WRITER_INSTANCE_ID | InfluxDB writer instance ID | |
## Deployment
The service itself is distributed as Docker container. Check the [`influxdb-writer`](https://github.com/absmach/magistrala/blob/main/docker/addons/influxdb-writer/docker-compose.yml#L35-L58) service section in docker-compose file to see how service is deployed.
To start the service, execute the following shell script:
```bash
# download the latest version of the service
git clone https://github.com/absmach/magistrala
cd magistrala
# compile the influxdb
make influxdb
# copy binary to bin
make install
# Set the environment variables and run the service
MG_INFLUX_WRITER_LOG_LEVEL=[Influx writer log level] \
MG_INFLUX_WRITER_CONFIG_PATH=[Config file path with Message broker subjects list, payload type and content-type] \
MG_INFLUX_WRITER_HTTP_HOST=[Service HTTP host] \
MG_INFLUX_WRITER_HTTP_PORT=[Service HTTP port] \
MG_INFLUX_WRITER_HTTP_SERVER_CERT=[Service HTTP server cert] \
MG_INFLUX_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \
MG_INFLUXDB_PROTOCOL=[InfluxDB protocol] \
MG_INFLUXDB_HOST=[InfluxDB database host] \
MG_INFLUXDB_PORT=[InfluxDB database port] \
MG_INFLUXDB_ADMIN_USER=[InfluxDB admin user] \
MG_INFLUXDB_ADMIN_PASSWORD=[InfluxDB admin password] \
MG_INFLUXDB_NAME=[InfluxDB database name] \
MG_INFLUXDB_BUCKET=[InfluxDB bucket] \
MG_INFLUXDB_ORG=[InfluxDB org] \
MG_INFLUXDB_TOKEN=[InfluxDB token] \
MG_INFLUXDB_DBURL=[InfluxDB database url] \
MG_INFLUXDB_USER_AGENT=[InfluxDB user agent] \
MG_INFLUXDB_TIMEOUT=[InfluxDB timeout] \
MG_INFLUXDB_INSECURE_SKIP_VERIFY=[InfluxDB insecure skip verify] \
MG_MESSAGE_BROKER_URL=[Message broker instance URL] \
MG_JAEGER_URL=[Jaeger server URL] \
MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \
MG_INFLUX_WRITER_INSTANCE_ID=[Influx writer instance ID] \
$GOBIN/magistrala-influxdb
```
### Using docker-compose
This service can be deployed using docker containers.
Docker compose file is available in `<project_root>/docker/addons/influxdb-writer/docker-compose.yml`. Besides database
and writer service, it contains InfluxData Web Admin Interface which can be used for database
exploration and data visualization and analytics. In order to run Magistrala InfluxDB writer, execute the following command:
```bash
docker compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
```
And, to use the default .env file, execute the following command:
```bash
docker compose -f docker/addons/influxdb-writer/docker-compose.yml up --env-file docker/.env -d
```
_Please note that you need to start core services before the additional ones._
## Usage
Starting service will start consuming normalized messages in SenML format.
Official docs can be found [here](https://docs.magistrala.abstractmachines.fr).
-164
View File
@@ -1,164 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb
import (
"context"
"math"
"time"
"github.com/absmach/magistrala/consumers"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
"github.com/influxdata/influxdb-client-go/v2/api/write"
)
const senmlPoints = "messages"
var errSaveMessage = errors.New("failed to save message to influxdb database")
var (
_ consumers.AsyncConsumer = (*influxRepo)(nil)
_ consumers.BlockingConsumer = (*influxRepo)(nil)
)
type RepoConfig struct {
Bucket string
Org string
}
type influxRepo struct {
client influxdb2.Client
cfg RepoConfig
errCh chan error
writeAPI api.WriteAPI
writeAPIBlocking api.WriteAPIBlocking
}
// NewSync returns new InfluxDB writer.
func NewSync(client influxdb2.Client, config RepoConfig) consumers.BlockingConsumer {
return &influxRepo{
client: client,
cfg: config,
writeAPI: nil,
writeAPIBlocking: client.WriteAPIBlocking(config.Org, config.Bucket),
}
}
func NewAsync(client influxdb2.Client, config RepoConfig) consumers.AsyncConsumer {
return &influxRepo{
client: client,
cfg: config,
errCh: make(chan error, 1),
writeAPI: client.WriteAPI(config.Org, config.Bucket),
writeAPIBlocking: nil,
}
}
func (repo *influxRepo) ConsumeAsync(_ context.Context, message interface{}) {
var err error
var pts []*write.Point
switch m := message.(type) {
case json.Messages:
pts, err = repo.jsonPoints(m)
default:
pts, err = repo.senmlPoints(m)
}
if err != nil {
repo.errCh <- err
return
}
done := make(chan bool)
defer close(done)
go func(done <-chan bool) {
for {
select {
case err := <-repo.writeAPI.Errors():
repo.errCh <- err
case <-done:
repo.errCh <- nil // pass nil error to the error channel
return
}
}
}(done)
for _, pt := range pts {
repo.writeAPI.WritePoint(pt)
}
repo.writeAPI.Flush()
}
func (repo *influxRepo) Errors() <-chan error {
if repo.errCh != nil {
return repo.errCh
}
return nil
}
func (repo *influxRepo) ConsumeBlocking(ctx context.Context, message interface{}) error {
var err error
var pts []*write.Point
switch m := message.(type) {
case json.Messages:
pts, err = repo.jsonPoints(m)
default:
pts, err = repo.senmlPoints(m)
}
if err != nil {
return err
}
return repo.writeAPIBlocking.WritePoint(ctx, pts...)
}
func (repo *influxRepo) senmlPoints(messages interface{}) ([]*write.Point, error) {
msgs, ok := messages.([]senml.Message)
if !ok {
return nil, errSaveMessage
}
var pts []*write.Point
for _, msg := range msgs {
tgs, flds := senmlTags(msg), senmlFields(msg)
sec, dec := math.Modf(msg.Time)
t := time.Unix(int64(sec), int64(dec*(1e9)))
pt := influxdb2.NewPoint(senmlPoints, tgs, flds, t)
pts = append(pts, pt)
}
return pts, nil
}
func (repo *influxRepo) jsonPoints(msgs json.Messages) ([]*write.Point, error) {
var pts []*write.Point
for i, m := range msgs.Data {
t := time.Unix(0, m.Created+int64(i))
flat, err := json.Flatten(m.Payload)
if err != nil {
return nil, errors.Wrap(json.ErrTransform, err)
}
m.Payload = flat
// Copy first-level fields so that the original Payload is unchanged.
fields := make(map[string]interface{})
for k, v := range m.Payload {
fields[k] = v
}
// At least one known field need to exist so that COUNT can be performed.
fields["protocol"] = m.Protocol
pt := influxdb2.NewPoint(msgs.Format, jsonTags(m), fields, t)
pts = append(pts, pt)
}
return pts, nil
}
-476
View File
@@ -1,476 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb_test
import (
"context"
"fmt"
"os"
"testing"
"time"
writer "github.com/absmach/magistrala/consumers/writers/influxdb"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
"github.com/absmach/magistrala/pkg/uuid"
influxdata "github.com/influxdata/influxdb-client-go/v2"
"github.com/stretchr/testify/assert"
)
const valueFields = 5
var (
testLog, _ = mglog.New(os.Stdout, "info")
streamsSize = 250
rowCountSenml = fmt.Sprintf(`from(bucket: "%s")
|> range(start: -1h, stop: 1h)
|> filter(fn: (r) => r["_measurement"] == "messages")
|> filter(fn: (r) => r["_field"] == "dataValue" or r["_field"] == "stringValue" or r["_field"] == "value" or r["_field"] == "boolValue" or r["_field"] == "sum" )
|> group(columns: ["_measurement"])
|> count()
|> yield(name: "count")`, repoCfg.Bucket)
rowCountJSON = fmt.Sprintf(`from(bucket: "%s")
|> range(start: -1h, stop: 1h)
|> filter(fn: (r) => r["_measurement"] == "some_json")
|> filter(fn: (r) => r["_field"] == "field_1" or r["_field"] == "field_2" or r["_field"] == "field_3" or r["_field"] == "field_4" or r["_field"] == "field_5/field_1" or r["_field"] == "field_5/field_2")
|> count()
|> yield(name: "count")`, repoCfg.Bucket)
subtopic = "topic"
client influxdata.Client
v float64 = 5
stringV = "value"
boolV = true
dataV = "base64"
sum float64 = 42
repoCfg = writer.RepoConfig{
Bucket: dbBucket,
Org: dbOrg,
}
errUnexpectedType = errors.New("Unexpected response type")
idProvider = uuid.New()
)
func deleteBucket() error {
bucketsAPI := client.BucketsAPI()
bucket, err := bucketsAPI.FindBucketByName(context.Background(), repoCfg.Bucket)
if err != nil {
return err
}
if err = bucketsAPI.DeleteBucket(context.Background(), bucket); err != nil {
return err
}
return nil
}
func createBucket() error {
orgAPI := client.OrganizationsAPI()
org, err := orgAPI.FindOrganizationByName(context.Background(), repoCfg.Org)
if err != nil {
return err
}
bucketsAPI := client.BucketsAPI()
if _, err = bucketsAPI.CreateBucketWithName(context.Background(), org, repoCfg.Bucket); err != nil {
return err
}
return nil
}
func resetBucket() error {
if err := deleteBucket(); err != nil {
return err
}
if err := createBucket(); err != nil {
return err
}
return nil
}
func queryDB(fluxQuery string) (int, error) {
rowCount := 0
queryAPI := client.QueryAPI(repoCfg.Org)
// get QueryTableResult
result, err := queryAPI.Query(context.Background(), fluxQuery)
if err != nil {
return rowCount, err
}
if result.Next() {
value, ok := result.Record().Value().(int64)
if !ok {
return rowCount, errUnexpectedType
}
rowCount = int(value)
}
if result.Err() != nil {
return rowCount, result.Err()
}
return rowCount, nil
}
func TestAsyncSaveSenml(t *testing.T) {
asyncRepo := writer.NewAsync(client, repoCfg)
cases := []struct {
desc string
msgsNum int
expectedSize int
}{
{
desc: "save a single message",
msgsNum: 1,
expectedSize: 1,
},
{
desc: "save a batch of messages",
msgsNum: streamsSize,
expectedSize: streamsSize,
},
}
for _, tc := range cases {
err := resetBucket()
assert.Nil(t, err, fmt.Sprintf("Cleaning data from InfluxDB expected to succeed: %s.\n", err))
now := time.Now().UnixNano()
var msgs []senml.Message
chanID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s\n", err))
pubID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s\n", err))
for i := 0; i < tc.msgsNum; i++ {
msg := senml.Message{
Channel: chanID,
Publisher: pubID,
Protocol: "http",
Name: "test name",
Unit: "km",
UpdateTime: 5456565466,
}
// Mix possible values as well as value sum.
count := i % valueFields
switch count {
case 0:
msg.Subtopic = subtopic
msg.Value = &v
case 1:
msg.BoolValue = &boolV
case 2:
msg.StringValue = &stringV
case 3:
msg.DataValue = &dataV
case 4:
msg.Sum = &sum
}
msg.Time = float64(now)/float64(1e9) - float64(i)
msgs = append(msgs, msg)
}
errs := asyncRepo.Errors()
asyncRepo.ConsumeAsync(context.TODO(), msgs)
err = <-errs
assert.Nil(t, err, fmt.Sprintf("Save operation expected to succeed: %s.\n", err))
count, err := queryDB(rowCountSenml)
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data expected to succeed: %s.\n", err))
assert.Equal(t, tc.expectedSize, count, fmt.Sprintf("Expected to have %d messages saved, found %d instead.\n", tc.expectedSize, count))
}
}
func TestBlockingSaveSenml(t *testing.T) {
syncRepo := writer.NewSync(client, repoCfg)
cases := []struct {
desc string
msgsNum int
expectedSize int
}{
{
desc: "save a single message",
msgsNum: 1,
expectedSize: 1,
},
{
desc: "save a batch of messages",
msgsNum: streamsSize,
expectedSize: streamsSize,
},
}
for _, tc := range cases {
err := resetBucket()
assert.Nil(t, err, fmt.Sprintf("Cleaning data from InfluxDB expected to succeed: %s.\n", err))
now := time.Now().UnixNano()
var msgs []senml.Message
chanID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s\n", err))
pubID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s\n", err))
for i := 0; i < tc.msgsNum; i++ {
msg := senml.Message{
Channel: chanID,
Publisher: pubID,
Protocol: "http",
Name: "test name",
Unit: "km",
UpdateTime: 5456565466,
}
// Mix possible values as well as value sum.
count := i % valueFields
switch count {
case 0:
msg.Subtopic = subtopic
msg.Value = &v
case 1:
msg.BoolValue = &boolV
case 2:
msg.StringValue = &stringV
case 3:
msg.DataValue = &dataV
case 4:
msg.Sum = &sum
}
msg.Time = float64(now)/float64(1e9) - float64(i)
msgs = append(msgs, msg)
}
err = syncRepo.ConsumeBlocking(context.TODO(), msgs)
assert.Nil(t, err, fmt.Sprintf("Save operation expected to succeed: %s.\n", err))
count, err := queryDB(rowCountSenml)
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data expected to succeed: %s.\n", err))
assert.Equal(t, tc.expectedSize, count, fmt.Sprintf("Expected to have %d messages saved, found %d instead.\n", tc.expectedSize, count))
}
}
func TestAsyncSaveJSON(t *testing.T) {
asyncRepo := writer.NewAsync(client, repoCfg)
chanID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
pubID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
msg := json.Message{
Channel: chanID,
Publisher: pubID,
Created: time.Now().UnixNano(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
},
}
invalidKeySepMsg := msg
invalidKeySepMsg.Payload = map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
"field_6/field_7": "value",
}
invalidKeyNameMsg := msg
invalidKeyNameMsg.Payload = map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
"publisher": "value",
}
now := time.Now().UnixNano()
msgs := json.Messages{
Format: "some_json",
}
invalidKeySepMsgs := json.Messages{
Format: "some_json",
}
invalidKeyNameMsgs := json.Messages{
Format: "some_json",
}
for i := 0; i < streamsSize; i++ {
msg.Created = now
msgs.Data = append(msgs.Data, msg)
invalidKeySepMsgs.Data = append(invalidKeySepMsgs.Data, invalidKeySepMsg)
invalidKeyNameMsgs.Data = append(invalidKeyNameMsgs.Data, invalidKeyNameMsg)
}
cases := []struct {
desc string
msgs json.Messages
err error
}{
{
desc: "consume valid json messages",
msgs: msgs,
err: nil,
},
{
desc: "consume invalid json messages containing invalid key separator",
msgs: invalidKeySepMsgs,
err: json.ErrInvalidKey,
},
{
desc: "consume invalid json messages containing invalid key name",
msgs: invalidKeySepMsgs,
err: json.ErrInvalidKey,
},
}
for _, tc := range cases {
err := resetBucket()
assert.Nil(t, err, fmt.Sprintf("Cleaning data from InfluxDB expected to succeed: %s.\n", err))
asyncRepo.ConsumeAsync(context.TODO(), msgs)
timer := time.NewTimer(1 * time.Millisecond)
select {
case err = <-asyncRepo.Errors():
case <-timer.C:
t.Error("errors channel blocked, nothing returned.")
}
switch err {
case nil:
count, err := queryDB(rowCountJSON)
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data expected to succeed: %s.\n", err))
assert.Equal(t, streamsSize, count, fmt.Sprintf("Expected to have %d messages saved, found %d instead.\n", streamsSize, count))
default:
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
}
}
}
func TestBlockingSaveJSON(t *testing.T) {
syncRepo := writer.NewSync(client, repoCfg)
chanID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
pubID, err := idProvider.ID()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
msg := json.Message{
Channel: chanID,
Publisher: pubID,
Created: time.Now().UnixNano(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
},
}
invalidKeySepMsg := msg
invalidKeySepMsg.Payload = map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
"field_6/field_7": "value",
}
invalidKeyNameMsg := msg
invalidKeyNameMsg.Payload = map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
"publisher": "value",
}
now := time.Now().UnixNano()
msgs := json.Messages{
Format: "some_json",
}
invalidKeySepMsgs := json.Messages{
Format: "some_json",
}
invalidKeyNameMsgs := json.Messages{
Format: "some_json",
}
for i := 0; i < streamsSize; i++ {
msg.Created = now
msgs.Data = append(msgs.Data, msg)
invalidKeySepMsgs.Data = append(invalidKeySepMsgs.Data, invalidKeySepMsg)
invalidKeyNameMsgs.Data = append(invalidKeyNameMsgs.Data, invalidKeyNameMsg)
}
cases := []struct {
desc string
msgs json.Messages
err error
}{
{
desc: "consume valid json messages",
msgs: msgs,
err: nil,
},
{
desc: "consume invalid json messages containing invalid key separator",
msgs: invalidKeySepMsgs,
err: json.ErrInvalidKey,
},
{
desc: "consume invalid json messages containing invalid key name",
msgs: invalidKeySepMsgs,
err: json.ErrInvalidKey,
},
}
for _, tc := range cases {
err := resetBucket()
assert.Nil(t, err, fmt.Sprintf("Cleaning data from InfluxDB expected to succeed: %s.\n", err))
switch err = syncRepo.ConsumeBlocking(context.TODO(), tc.msgs); err {
case nil:
count, err := queryDB(rowCountJSON)
assert.Nil(t, err, fmt.Sprintf("Querying InfluxDB to retrieve data expected to succeed: %s.\n", err))
assert.Equal(t, streamsSize, count, fmt.Sprintf("Expected to have %d messages saved, found %d instead.\n", streamsSize, count))
default:
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
}
}
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package influxdb contains the domain concept definitions needed to
// support Magistrala InfluxDB writer service functionality.
package influxdb
-35
View File
@@ -1,35 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb
import (
"github.com/absmach/magistrala/pkg/transformers/senml"
)
type fields map[string]interface{}
func senmlFields(msg senml.Message) fields {
ret := fields{
"protocol": msg.Protocol,
"unit": msg.Unit,
"updateTime": msg.UpdateTime,
}
switch {
case msg.Value != nil:
ret["value"] = *msg.Value
case msg.StringValue != nil:
ret["stringValue"] = *msg.StringValue
case msg.DataValue != nil:
ret["dataValue"] = *msg.DataValue
case msg.BoolValue != nil:
ret["boolValue"] = *msg.BoolValue
}
if msg.Sum != nil {
ret["sum"] = *msg.Sum
}
return ret
}
-95
View File
@@ -1,95 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb_test
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"testing"
"time"
influxdata "github.com/influxdata/influxdb-client-go/v2"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
)
const (
dbToken = "test-token"
dbOrg = "test-org"
dbAdmin = "test-admin"
dbPass = "test-password"
dbBucket = "test-bucket"
dbInitMode = "setup"
dbFluxEnabled = "true"
dbBindAddress = ":8088"
port = "8086/tcp"
db = "influxdb"
dbVersion = "2.7-alpine"
poolMaxWait = 120 * time.Second
)
var address string
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
container, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: db,
Tag: dbVersion,
Env: []string{
fmt.Sprintf("DOCKER_INFLUXDB_INIT_MODE=%s", dbInitMode),
fmt.Sprintf("DOCKER_INFLUXDB_INIT_USERNAME=%s", dbAdmin),
fmt.Sprintf("DOCKER_INFLUXDB_INIT_PASSWORD=%s", dbPass),
fmt.Sprintf("DOCKER_INFLUXDB_INIT_ORG=%s", dbOrg),
fmt.Sprintf("DOCKER_INFLUXDB_INIT_BUCKET=%s", dbBucket),
fmt.Sprintf("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=%s", dbToken),
fmt.Sprintf("INFLUXDB_HTTP_FLUX_ENABLED=%s", dbFluxEnabled),
fmt.Sprintf("INFLUXDB_BIND_ADDRESS=%s", dbBindAddress),
},
}, func(config *docker.HostConfig) {
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
handleInterrupt(pool, container)
address = fmt.Sprintf("%s:%s", "http://localhost", container.GetPort(port))
pool.MaxWait = poolMaxWait
if err := pool.Retry(func() error {
client = influxdata.NewClientWithOptions(address, dbToken, influxdata.DefaultOptions())
_, err = client.Ready(context.Background())
return err
}); err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
code := m.Run()
if err := pool.Purge(container); err != nil {
testLog.Error(fmt.Sprintf("Could not purge container: %s", err))
}
os.Exit(code)
}
func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(0)
}()
}
-28
View File
@@ -1,28 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb
import (
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
)
type tags map[string]string
func senmlTags(msg senml.Message) tags {
return tags{
"channel": msg.Channel,
"subtopic": msg.Subtopic,
"publisher": msg.Publisher,
"name": msg.Name,
}
}
func jsonTags(msg json.Message) tags {
return tags{
"channel": msg.Channel,
"subtopic": msg.Subtopic,
"publisher": msg.Publisher,
}
}
-65
View File
@@ -1,65 +0,0 @@
# MongoDB writer
MongoDB writer provides message repository implementation for MongoDB.
## 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 |
| -------------------------------- | --------------------------------------------------------------------------------- | ------------------------------ |
| MG_MONGO_WRITER_LOG_LEVEL | Log level for MongoDB writer | info |
| MG_MONGO_WRITER_CONFIG_PATH | Config file path with Message broker subjects list, payload type and content-type | /config.toml |
| MG_MONGO_WRITER_HTTP_HOST | Service HTTP host | localhost |
| MG_MONGO_WRITER_HTTP_PORT | Service HTTP port | 9010 |
| MG_MONGO_WRITER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" |
| MG_MONGO_WRITER_HTTP_SERVER_KEY | Service HTTP server key | "" |
| MG_MONGO_NAME | Default MongoDB database name | messages |
| MG_MONGO_HOST | Default MongoDB database host | localhost |
| MG_MONGO_PORT | Default MongoDB database port | 27017 |
| MG_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 |
| MG_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_MONGO_WRITER_INSTANCE_ID | MongoDB writer instance ID | "" |
## Deployment
The service itself is distributed as Docker container. Check the [`mongodb-writer`](https://github.com/absmach/magistrala/blob/main/docker/addons/mongodb-writer/docker-compose.yml#L36-L55) service section in docker-compose file to see how service is deployed.
To start the service, execute the following shell script:
```bash
# download the latest version of the service
git clone https://github.com/absmach/magistrala
cd magistrala
# compile the mongodb writer
make mongodb-writer
# copy binary to bin
make install
# Set the environment variables and run the service
MG_MONGO_WRITER_LOG_LEVEL=[MongoDB writer log level] \
MG_MONGO_WRITER_CONFIG_PATH=[Configuration file path with Message broker subjects list] \
MG_MONGO_WRITER_HTTP_HOST=[Service HTTP host] \
MG_MONGO_WRITER_HTTP_PORT=[Service HTTP port] \
MG_MONGO_WRITER_HTTP_SERVER_CERT=[Service HTTP server certificate] \
MG_MONGO_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \
MG_MONGO_NAME=[MongoDB database name] \
MG_MONGO_HOST=[MongoDB database host] \
MG_MONGO_PORT=[MongoDB database port] \
MG_MESSAGE_BROKER_URL=[Message broker instance URL] \
MG_JAEGER_URL=[Jaeger server URL] \
MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \
MG_MONGO_WRITER_INSTANCE_ID=[MongoDB writer instance ID] \
$GOBIN/magistrala-mongodb-writer
```
## Usage
Starting service will start consuming normalized messages in SenML format.
-84
View File
@@ -1,84 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mongodb
import (
"context"
"github.com/absmach/magistrala/consumers"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
const senmlCollection string = "messages"
var errSaveMessage = errors.New("failed to save message to mongodb database")
var _ consumers.BlockingConsumer = (*mongoRepo)(nil)
type mongoRepo struct {
db *mongo.Database
}
// New returns new MongoDB writer.
func New(db *mongo.Database) consumers.BlockingConsumer {
return &mongoRepo{db}
}
func (repo *mongoRepo) ConsumeBlocking(ctx context.Context, message interface{}) error {
switch m := message.(type) {
case json.Messages:
return repo.saveJSON(ctx, m)
default:
return repo.saveSenml(ctx, m)
}
}
func (repo *mongoRepo) saveSenml(ctx context.Context, messages interface{}) error {
msgs, ok := messages.([]senml.Message)
if !ok {
return errSaveMessage
}
coll := repo.db.Collection(senmlCollection)
var dbMsgs []interface{}
for _, msg := range msgs {
// Check if message is already in database.
filter := bson.M{"time": msg.Time, "publisher": msg.Publisher, "subtopic": msg.Subtopic, "name": msg.Name}
count, err := coll.CountDocuments(ctx, filter)
if err != nil {
return errors.Wrap(errSaveMessage, err)
}
if count == 0 {
dbMsgs = append(dbMsgs, msg)
}
}
_, err := coll.InsertMany(ctx, dbMsgs)
if err != nil {
return errors.Wrap(errSaveMessage, err)
}
return nil
}
func (repo *mongoRepo) saveJSON(ctx context.Context, msgs json.Messages) error {
m := []interface{}{}
for _, msg := range msgs.Data {
m = append(m, msg)
}
coll := repo.db.Collection(msgs.Format)
_, err := coll.InsertMany(ctx, m)
if err != nil {
return errors.Wrap(errSaveMessage, err)
}
return nil
}
-134
View File
@@ -1,134 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mongodb_test
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/absmach/magistrala/consumers/writers/mongodb"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/transformers/json"
"github.com/absmach/magistrala/pkg/transformers/senml"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
port string
addr string
testLog, _ = mglog.New(os.Stdout, "info")
testDB = "test"
collection = "messages"
msgsNum = 100
valueFields = 5
subtopic = "topic"
)
var (
v float64 = 5
stringV = "value"
boolV = true
dataV = "base64"
sum float64 = 42
)
func TestSaveSenml(t *testing.T) {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
require.Nil(t, err, fmt.Sprintf("Creating new MongoDB client expected to succeed: %s.\n", err))
db := client.Database(testDB)
repo := mongodb.New(db)
now := time.Now().Unix()
msg := senml.Message{
Channel: "45",
Publisher: "2580",
Protocol: "http",
Name: "test name",
Unit: "km",
Time: 13451312,
UpdateTime: 5456565466,
}
var msgs []senml.Message
for i := 0; i < msgsNum; i++ {
// Mix possible values as well as value sum.
count := i % valueFields
switch count {
case 0:
msg.Subtopic = subtopic
msg.Value = &v
case 1:
msg.BoolValue = &boolV
case 2:
msg.StringValue = &stringV
case 3:
msg.DataValue = &dataV
case 4:
msg.Sum = &sum
}
msg.Time = float64(now + int64(i))
msgs = append(msgs, msg)
}
err = repo.ConsumeBlocking(context.TODO(), msgs)
require.Nil(t, err, fmt.Sprintf("Save operation expected to succeed: %s.\n", err))
count, err := db.Collection(collection).CountDocuments(context.Background(), bson.D{})
assert.Nil(t, err, fmt.Sprintf("Querying database expected to succeed: %s.\n", err))
assert.Equal(t, int64(msgsNum), count, fmt.Sprintf("Expected to have %d value, found %d instead.\n", msgsNum, count))
}
func TestSaveJSON(t *testing.T) {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
require.Nil(t, err, fmt.Sprintf("Creating new MongoDB client expected to succeed: %s.\n", err))
db := client.Database(testDB)
repo := mongodb.New(db)
chid, err := uuid.NewV4()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
pubid, err := uuid.NewV4()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
msg := json.Message{
Channel: chid.String(),
Publisher: pubid.String(),
Created: time.Now().Unix(),
Subtopic: "subtopic/format/some_json",
Protocol: "mqtt",
Payload: map[string]interface{}{
"field_1": 123,
"field_2": "value",
"field_3": false,
"field_4": 12.344,
"field_5": map[string]interface{}{
"field_1": "value",
"field_2": 42,
},
},
}
now := time.Now().Unix()
msgs := json.Messages{
Format: "some_json",
}
for i := 0; i < msgsNum; i++ {
msg.Created = now + int64(i)
msgs.Data = append(msgs.Data, msg)
}
err = repo.ConsumeBlocking(context.TODO(), msgs)
assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err))
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package mongodb contains the domain concept definitions needed to
// support Magistrala MondoDB writer service functionality.
package mongodb
-56
View File
@@ -1,56 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mongodb_test
import (
"context"
"fmt"
"log"
"os"
"testing"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
container, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "mongo",
Tag: "7.0.5",
Env: []string{
"MONGO_INITDB_DATABASE=test",
},
}, func(config *docker.HostConfig) {
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
port = container.GetPort("27017/tcp")
addr = fmt.Sprintf("mongodb://localhost:%s", port)
if err := pool.Retry(func() error {
_, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
return err
}); err != nil {
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
}
code := m.Run()
if err := pool.Purge(container); err != nil {
testLog.Error(fmt.Sprintf("Could not purge container: %s", err))
}
os.Exit(code)
}
-169
View File
@@ -389,113 +389,6 @@ MG_CERTS_DB_SSL_KEY=
MG_CERTS_DB_SSL_ROOT_CERT=
MG_CERTS_INSTANCE_ID=
### LoRa
MG_LORA_ADAPTER_LOG_LEVEL=debug
MG_LORA_ADAPTER_MESSAGES_URL=tcp://magistrala-mqtt:1883
MG_LORA_ADAPTER_MESSAGES_TOPIC=application/+/device/+/event/up
MG_LORA_ADAPTER_MESSAGES_USER=
MG_LORA_ADAPTER_MESSAGES_PASS=
MG_LORA_ADAPTER_MESSAGES_TIMEOUT=30s
MG_LORA_ADAPTER_EVENT_CONSUMER=lora-adapter
MG_LORA_ADAPTER_HTTP_HOST=lora-adapter
MG_LORA_ADAPTER_HTTP_PORT=9017
MG_LORA_ADAPTER_HTTP_SERVER_CERT=
MG_LORA_ADAPTER_HTTP_SERVER_KEY=
MG_LORA_ADAPTER_ROUTE_MAP_URL=redis://lora-redis:${MG_REDIS_TCP_PORT}/0
MG_LORA_ADAPTER_INSTANCE_ID=
### OPC-UA
MG_OPCUA_ADAPTER_LOG_LEVEL=debug
MG_OPCUA_ADAPTER_EVENT_CONSUMER=opcua-adapter
MG_OPCUA_ADAPTER_HTTP_HOST=opcua-adapter
MG_OPCUA_ADAPTER_HTTP_PORT=8188
MG_OPCUA_ADAPTER_HTTP_SERVER_CERT=
MG_OPCUA_ADAPTER_HTTP_SERVER_KEY=
MG_OPCUA_ADAPTER_ROUTE_MAP_URL=redis://opcua-redis:${MG_REDIS_TCP_PORT}/0
MG_OPCUA_ADAPTER_INSTANCE_ID=
### Cassandra
MG_CASSANDRA_CLUSTER=magistrala-cassandra
MG_CASSANDRA_KEYSPACE=magistrala
MG_CASSANDRA_USER=magistrala
MG_CASSANDRA_PASS=magistrala
MG_CASSANDRA_PORT=9042
### Cassandra Writer
MG_CASSANDRA_WRITER_LOG_LEVEL=debug
MG_CASSANDRA_WRITER_CONFIG_PATH=/config.toml
MG_CASSANDRA_WRITER_HTTP_HOST=cassandra-writer
MG_CASSANDRA_WRITER_HTTP_PORT=9004
MG_CASSANDRA_WRITER_HTTP_SERVER_CERT=
MG_CASSANDRA_WRITER_HTTP_SERVER_KEY=
MG_CASSANDRA_WRITER_INSTANCE_ID=
### Cassandra Reader
MG_CASSANDRA_READER_LOG_LEVEL=debug
MG_CASSANDRA_READER_HTTP_HOST=cassandra-reader
MG_CASSANDRA_READER_HTTP_PORT=9003
MG_CASSANDRA_READER_HTTP_SERVER_CERT=
MG_CASSANDRA_READER_HTTP_SERVER_KEY=
MG_CASSANDRA_READER_INSTANCE_ID=
### InfluxDB
MG_INFLUXDB_PROTOCOL=http
MG_INFLUXDB_HOST=magistrala-influxdb
MG_INFLUXDB_PORT=8086
MG_INFLUXDB_ADMIN_USER=magistrala
MG_INFLUXDB_ADMIN_PASSWORD=magistrala
MG_INFLUXDB_NAME=magistrala
MG_INFLUXDB_BUCKET=magistrala-bucket
MG_INFLUXDB_ORG=magistrala
MG_INFLUXDB_TOKEN=magistrala-token
MG_INFLUXDB_DBURL=
MG_INFLUXDB_USER_AGENT=InfluxDBClient
MG_INFLUXDB_TIMEOUT=1s
MG_INFLUXDB_INSECURE_SKIP_VERIFY=false
MG_INFLUXDB_INIT_MODE=setup
MG_INFLUXDB_ADMIN_URL=http://${MG_INFLUXDB_HOST}:${MG_INFLUXDB_PORT}
MG_INFLUXDB_HTTP_ENABLED=true
### InfluxDB Writer
MG_INFLUX_WRITER_LOG_LEVEL=debug
MG_INFLUX_WRITER_CONFIG_PATH=/config.toml
MG_INFLUX_WRITER_HTTP_HOST=influxdb-writer
MG_INFLUX_WRITER_HTTP_PORT=9006
MG_INFLUX_WRITER_HTTP_SERVER_CERT=
MG_INFLUX_WRITER_HTTP_SERVER_KEY=
MG_INFLUX_WRITER_INSTANCE_ID=
### InfluxDB Reader
MG_INFLUX_READER_LOG_LEVEL=debug
MG_INFLUX_READER_HTTP_HOST=influxdb-reader
MG_INFLUX_READER_HTTP_PORT=9005
MG_INFLUX_READER_HTTP_SERVER_CERT=
MG_INFLUX_READER_HTTP_SERVER_KEY=
MG_INFLUX_READER_INSTANCE_ID=
### MongoDB
MG_MONGO_HOST=magistrala-mongodb
MG_MONGO_PORT=27017
MG_MONGO_NAME=magistrala
### MongoDB Writer
MG_MONGO_WRITER_LOG_LEVEL=debug
MG_MONGO_WRITER_CONFIG_PATH=/config.toml
MG_MONGO_WRITER_HTTP_HOST=mongodb-writer
MG_MONGO_WRITER_HTTP_PORT=9008
MG_MONGO_WRITER_HTTP_SERVER_CERT=
MG_MONGO_WRITER_HTTP_SERVER_KEY=
MG_MONGO_WRITER_INSTANCE_ID=
### MongoDB Reader
MG_MONGO_READER_LOG_LEVEL=debug
MG_MONGO_READER_HTTP_HOST=mongodb-reader
MG_MONGO_READER_HTTP_PORT=9007
MG_MONGO_READER_HTTP_SERVER_CERT=
MG_MONGO_READER_HTTP_SERVER_KEY=
MG_MONGO_READER_INSTANCE_ID=
### Postgres
MG_POSTGRES_HOST=magistrala-postgres
MG_POSTGRES_PORT=5432
@@ -552,68 +445,6 @@ MG_TIMESCALE_READER_HTTP_SERVER_CERT=
MG_TIMESCALE_READER_HTTP_SERVER_KEY=
MG_TIMESCALE_READER_INSTANCE_ID=
### Twins
MG_TWINS_LOG_LEVEL=debug
MG_TWINS_STANDALONE_ID=
MG_TWINS_STANDALONE_TOKEN=
MG_TWINS_CHANNEL_ID=
MG_TWINS_HTTP_HOST=twins
MG_TWINS_HTTP_PORT=9018
MG_TWINS_HTTP_SERVER_CERT=
MG_TWINS_HTTP_SERVER_KEY=
MG_TWINS_CACHE_URL=redis://twins-redis:${MG_REDIS_TCP_PORT}/0
MG_TWINS_DB_HOST=twins-db
MG_TWINS_DB_PORT=27018
MG_TWINS_DB_NAME=twins
MG_TWINS_INSTANCE_ID=
### SMTP Notifier
MG_SMTP_NOTIFIER_LOG_LEVEL=debug
MG_SMTP_NOTIFIER_CONFIG_PATH=/config.toml
MG_SMTP_NOTIFIER_FROM_ADDR=
MG_SMTP_NOTIFIER_HTTP_HOST=smtp-notifier
MG_SMTP_NOTIFIER_HTTP_PORT=9015
MG_SMTP_NOTIFIER_HTTP_SERVER_CERT=
MG_SMTP_NOTIFIER_HTTP_SERVER_KEY=
MG_SMTP_NOTIFIER_DB_HOST=smtp-notifier-db
MG_SMTP_NOTIFIER_DB_PORT=5432
MG_SMTP_NOTIFIER_DB_USER=magistrala
MG_SMTP_NOTIFIER_DB_PASS=magistrala
MG_SMTP_NOTIFIER_DB_NAME=subscriptions
MG_SMTP_NOTIFIER_DB_SSL_MODE=disable
MG_SMTP_NOTIFIER_DB_SSL_CERT=
MG_SMTP_NOTIFIER_DB_SSL_KEY=
MG_SMTP_NOTIFIER_DB_SSL_ROOT_CERT=
MG_SMTP_NOTIFIER_EMAIL_TEMPLATE=smtp-notifier.tmpl
MG_SMTP_NOTIFIER_INSTANCE_ID=
### SMPP Notifier
MG_SMPP_NOTIFIER_LOG_LEVEL=debug
MG_SMPP_NOTIFIER_FROM_ADDR=
MG_SMPP_NOTIFIER_CONFIG_PATH=/config.toml
MG_SMPP_NOTIFIER_HTTP_HOST=smpp-notifier
MG_SMPP_NOTIFIER_HTTP_PORT=9014
MG_SMPP_NOTIFIER_HTTP_SERVER_CERT=
MG_SMPP_NOTIFIER_HTTP_SERVER_KEY=
MG_SMPP_NOTIFIER_DB_HOST=smpp-notifier-db
MG_SMPP_NOTIFIER_DB_PORT=5432
MG_SMPP_NOTIFIER_DB_USER=magistrala
MG_SMPP_NOTIFIER_DB_PASS=magistrala
MG_SMPP_NOTIFIER_DB_NAME=subscriptions
MG_SMPP_NOTIFIER_DB_SSL_MODE=disable
MG_SMPP_NOTIFIER_DB_SSL_CERT=
MG_SMPP_NOTIFIER_DB_SSL_KEY=
MG_SMPP_NOTIFIER_DB_SSL_ROOT_CERT=
MG_SMPP_ADDRESS=localhost:2775
MG_SMPP_USERNAME=
MG_SMPP_PASSWORD=
MG_SMPP_SYSTEM_TYPE=
MG_SMPP_SRC_ADDR_TON=5
MG_SMPP_DST_ADDR_TON=1
MG_SMPP_SRC_ADDR_NPI=0
MG_SMPP_DST_ADDR_NPI=1
MG_SMPP_NOTIFIER_INSTANCE_ID=
### Journal
MG_JOURNAL_LOG_LEVEL=info
MG_JOURNAL_HTTP_HOST=journal
@@ -1,77 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional cassandra-reader. Since it's optional, this file is
# dependent of docker-compose file from <project_root>/docker. In order to run this service, execute command:
# docker compose -f docker/docker-compose.yml -f docker/addons/cassandra-reader/docker-compose.yml up
# from project root.
networks:
magistrala-base-net:
services:
cassandra-reader:
image: magistrala/cassandra-reader:${MG_RELEASE_TAG}
container_name: magistrala-cassandra-reader
restart: on-failure
environment:
MG_CASSANDRA_READER_LOG_LEVEL: ${MG_CASSANDRA_READER_LOG_LEVEL}
MG_CASSANDRA_READER_HTTP_HOST: ${MG_CASSANDRA_READER_HTTP_HOST}
MG_CASSANDRA_READER_HTTP_PORT: ${MG_CASSANDRA_READER_HTTP_PORT}
MG_CASSANDRA_READER_HTTP_SERVER_CERT: ${MG_CASSANDRA_READER_HTTP_SERVER_CERT}
MG_CASSANDRA_READER_HTTP_SERVER_KEY: ${MG_CASSANDRA_READER_HTTP_SERVER_KEY}
MG_CASSANDRA_PORT: ${MG_CASSANDRA_PORT}
MG_CASSANDRA_CLUSTER: ${MG_CASSANDRA_CLUSTER}
MG_CASSANDRA_KEYSPACE: ${MG_CASSANDRA_KEYSPACE}
MG_CASSANDRA_USER: ${MG_CASSANDRA_USER}
MG_CASSANDRA_PASS: ${MG_CASSANDRA_PASS}
MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL}
MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT}
MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt}
MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key}
MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_CASSANDRA_READER_INSTANCE_ID: ${MG_CASSANDRA_READER_INSTANCE_ID}
ports:
- ${MG_CASSANDRA_READER_HTTP_PORT}:${MG_CASSANDRA_READER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ../../ssl/certs:/etc/ssl/certs
# Auth gRPC client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
# Things gRPC mTLS client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
@@ -1,19 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# To listen all messsage broker subjects use default value "channels.>".
# To subscribe to specific subjects use values starting by "channels." and
# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]).
[subscriber]
subjects = ["channels.>"]
[transformer]
# SenML or JSON
format = "senml"
# Used if format is SenML
content_type = "application/senml+json"
# Used as timestamp fields if format is JSON
time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"},
{ field_name = "millis_key", field_format = "unix_ms", location = "UTC"},
{ field_name = "micros_key", field_format = "unix_us", location = "UTC"},
{ field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}]
@@ -1,66 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional Cassandra and cassandra-writer. Since these are optional, this file is
# dependent of docker-compose file from <project_root>/docker. In order to run these services, execute command:
# docker compose -f docker/docker-compose.yml -f docker/addons/cassandra-writer/docker-compose.yml up
# from project root.
networks:
magistrala-base-net:
volumes:
magistrala-cassandra-volume:
services:
cassandra:
image: cassandra:3.11.16
container_name: magistrala-cassandra
restart: on-failure
networks:
- magistrala-base-net
ports:
- ${MG_CASSANDRA_PORT}:${MG_CASSANDRA_PORT}
volumes:
- magistrala-cassandra-volume:/var/lib/cassandra
cassandra-init-keyspace:
image: cassandra:3.11.16
depends_on:
- cassandra
restart: on-failure
volumes:
- ./init.sh:/init.sh
entrypoint: ["/init.sh"]
networks:
- magistrala-base-net
cassandra-writer:
image: magistrala/cassandra-writer:${MG_RELEASE_TAG}
container_name: magistrala-cassandra-writer
depends_on:
- cassandra
restart: on-failure
environment:
MG_CASSANDRA_WRITER_LOG_LEVEL: ${MG_CASSANDRA_WRITER_LOG_LEVEL}
MG_CASSANDRA_WRITER_CONFIG_PATH: ${MG_CASSANDRA_WRITER_CONFIG_PATH}
MG_CASSANDRA_WRITER_HTTP_HOST: ${MG_CASSANDRA_WRITER_HTTP_HOST}
MG_CASSANDRA_WRITER_HTTP_PORT: ${MG_CASSANDRA_WRITER_HTTP_PORT}
MG_CASSANDRA_WRITER_HTTP_SERVER_CERT: ${MG_CASSANDRA_WRITER_HTTP_SERVER_CERT}
MG_CASSANDRA_WRITER_HTTP_SERVER_KEY: ${MG_CASSANDRA_WRITER_HTTP_SERVER_KEY}
MG_CASSANDRA_PORT: ${MG_CASSANDRA_PORT}
MG_CASSANDRA_CLUSTER: ${MG_CASSANDRA_CLUSTER}
MG_CASSANDRA_KEYSPACE: ${MG_CASSANDRA_KEYSPACE}
MG_CASSANDRA_USER: ${MG_CASSANDRA_USER}
MG_CASSANDRA_PASS: ${MG_CASSANDRA_PASS}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_CASSANDRA_WRITER_INSTANCE_ID: ${MG_CASSANDRA_WRITER_INSTANCE_ID}
ports:
- ${MG_CASSANDRA_WRITER_HTTP_PORT}:${MG_CASSANDRA_WRITER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./config.toml:/config.toml
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
until printf "" 2>>/dev/null >>/dev/tcp/magistrala-cassandra/9042; do
sleep 5;
echo "Waiting for cassandra...";
done
echo "Creating keyspace and table..."
cqlsh magistrala-cassandra -e "CREATE KEYSPACE IF NOT EXISTS magistrala WITH replication = {'class':'SimpleStrategy','replication_factor':'1'};"
@@ -1,87 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
###
# This docker-compose file contains optional InfluxDB-reader service for the Magistrala
# platform. Since this service is optional, this file is dependent on the docker-compose.yml
# file from <project_root>/docker/. In order to run this service, core services,
# as well as the network from the core composition, should be already running.
###
networks:
magistrala-base-net:
services:
influxdb-reader:
image: magistrala/influxdb-reader:${MG_RELEASE_TAG}
container_name: magistrala-influxdb-reader
restart: on-failure
environment:
MG_INFLUX_READER_LOG_LEVEL: ${MG_INFLUX_READER_LOG_LEVEL}
MG_INFLUX_READER_HTTP_HOST: ${MG_INFLUX_READER_HTTP_HOST}
MG_INFLUX_READER_HTTP_PORT: ${MG_INFLUX_READER_HTTP_PORT}
MG_INFLUX_READER_HTTP_SERVER_CERT: ${MG_INFLUX_READER_HTTP_SERVER_CERT}
MG_INFLUX_READER_HTTP_SERVER_KEY: ${MG_INFLUX_READER_HTTP_SERVER_KEY}
MG_INFLUXDB_PROTOCOL: ${MG_INFLUXDB_PROTOCOL}
MG_INFLUXDB_HOST: ${MG_INFLUXDB_HOST}
MG_INFLUXDB_PORT: ${MG_INFLUXDB_PORT}
MG_INFLUXDB_ADMIN_USER: ${MG_INFLUXDB_ADMIN_USER}
MG_INFLUXDB_ADMIN_PASSWORD: ${MG_INFLUXDB_ADMIN_PASSWORD}
MG_INFLUXDB_NAME: ${MG_INFLUXDB_NAME}
MG_INFLUXDB_BUCKET: ${MG_INFLUXDB_BUCKET}
MG_INFLUXDB_ORG: ${MG_INFLUXDB_ORG}
MG_INFLUXDB_TOKEN: ${MG_INFLUXDB_TOKEN}
MG_INFLUXDB_DBURL: ${MG_INFLUXDB_DBURL}
MG_INFLUXDB_USER_AGENT: ${MG_INFLUXDB_USER_AGENT}
MG_INFLUXDB_TIMEOUT: ${MG_INFLUXDB_TIMEOUT}
MG_INFLUXDB_INSECURE_SKIP_VERIFY: ${MG_INFLUXDB_INSECURE_SKIP_VERIFY}
MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL}
MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT}
MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt}
MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key}
MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_INFLUX_READER_INSTANCE_ID: ${MG_INFLUX_READER_INSTANCE_ID}
ports:
- ${MG_INFLUX_READER_HTTP_PORT}:${MG_INFLUX_READER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ../../ssl/certs:/etc/ssl/certs
# Auth gRPC client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
# Things gRPC mTLS client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
-19
View File
@@ -1,19 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# To listen all messsage broker subjects use default value "channels.>".
# To subscribe to specific subjects use values starting by "channels." and
# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]).
[subscriber]
subjects = ["channels.>"]
[transformer]
# SenML or JSON
format = "senml"
# Used if format is SenML
content_type = "application/senml+json"
# Used as timestamp fields if format is JSON
time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"},
{ field_name = "millis_key", field_format = "unix_ms", location = "UTC"},
{ field_name = "micros_key", field_format = "unix_us", location = "UTC"},
{ field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}]
@@ -1,72 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional InfluxDB and InfluxDB-writer services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
volumes:
magistrala-influxdb-volume:
services:
influxdb:
image: influxdb:2.7.5-alpine
container_name: magistrala-influxdb
restart: on-failure
environment:
DOCKER_INFLUXDB_INIT_MODE: ${MG_INFLUXDB_INIT_MODE}
DOCKER_INFLUXDB_INIT_USERNAME: ${MG_INFLUXDB_ADMIN_USER}
DOCKER_INFLUXDB_INIT_PASSWORD: ${MG_INFLUXDB_ADMIN_PASSWORD}
DOCKER_INFLUXDB_ADMIN_URL: ${MG_INFLUXDB_ADMIN_URL}
DOCKER_INFLUXDB_INIT_ORG: ${MG_INFLUXDB_ORG}
DOCKER_INFLUXDB_INIT_BUCKET: ${MG_INFLUXDB_BUCKET}
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${MG_INFLUXDB_TOKEN}
INFLUXDB_HTTP_FLUX_ENABLED: ${MG_INFLUXDB_HTTP_ENABLED}
networks:
- magistrala-base-net
ports:
- ${MG_INFLUXDB_PORT}:${MG_INFLUXDB_PORT}
volumes:
- magistrala-influxdb-volume:/var/lib/influxdb2
influxdb-writer:
image: magistrala/influxdb-writer:${MG_RELEASE_TAG}
container_name: magistrala-influxdb-writer
depends_on:
- influxdb
restart: on-failure
environment:
MG_INFLUX_WRITER_LOG_LEVEL: ${MG_INFLUX_WRITER_LOG_LEVEL}
MG_INFLUX_WRITER_CONFIG_PATH: ${MG_INFLUX_WRITER_CONFIG_PATH}
MG_INFLUX_WRITER_HTTP_HOST: ${MG_INFLUX_WRITER_HTTP_HOST}
MG_INFLUX_WRITER_HTTP_PORT: ${MG_INFLUX_WRITER_HTTP_PORT}
MG_INFLUX_WRITER_HTTP_SERVER_CERT: ${MG_INFLUX_WRITER_HTTP_SERVER_CERT}
MG_INFLUX_WRITER_HTTP_SERVER_KEY: ${MG_INFLUX_WRITER_HTTP_SERVER_KEY}
MG_INFLUXDB_PROTOCOL: ${MG_INFLUXDB_PROTOCOL}
MG_INFLUXDB_HOST: ${MG_INFLUXDB_HOST}
MG_INFLUXDB_PORT: ${MG_INFLUXDB_PORT}
MG_INFLUXDB_ADMIN_USER: ${MG_INFLUXDB_ADMIN_USER}
MG_INFLUXDB_ADMIN_PASSWORD: ${MG_INFLUXDB_ADMIN_PASSWORD}
MG_INFLUXDB_NAME: ${MG_INFLUXDB_NAME}
MG_INFLUXDB_BUCKET: ${MG_INFLUXDB_BUCKET}
MG_INFLUXDB_ORG: ${MG_INFLUXDB_ORG}
MG_INFLUXDB_TOKEN: ${MG_INFLUXDB_TOKEN}
MG_INFLUXDB_DBURL: ${MG_INFLUXDB_DBURL}
MG_INFLUXDB_USER_AGENT: ${MG_INFLUXDB_USER_AGENT}
MG_INFLUXDB_TIMEOUT: ${MG_INFLUXDB_TIMEOUT}
MG_INFLUXDB_INSECURE_SKIP_VERIFY: ${MG_INFLUXDB_INSECURE_SKIP_VERIFY}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_INFLUX_WRITER_INSTANCE_ID: ${MG_INFLUX_WRITER_INSTANCE_ID}
ports:
- ${MG_INFLUX_WRITER_HTTP_PORT}:${MG_INFLUX_WRITER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./config.toml:/config.toml
@@ -1,46 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional lora-adapter and lora-redis services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
services:
lora-redis:
image: redis:7.2.4-alpine
container_name: magistrala-lora-redis
restart: on-failure
networks:
- magistrala-base-net
lora-adapter:
image: magistrala/lora:${MG_RELEASE_TAG}
container_name: magistrala-lora
restart: on-failure
environment:
MG_LORA_ADAPTER_LOG_LEVEL: ${MG_LORA_ADAPTER_LOG_LEVEL}
MG_LORA_ADAPTER_MESSAGES_URL: ${MG_LORA_ADAPTER_MESSAGES_URL}
MG_LORA_ADAPTER_MESSAGES_TOPIC: ${MG_LORA_ADAPTER_MESSAGES_TOPIC}
MG_LORA_ADAPTER_MESSAGES_USER: ${MG_LORA_ADAPTER_MESSAGES_USER}
MG_LORA_ADAPTER_MESSAGES_PASS: ${MG_LORA_ADAPTER_MESSAGES_PASS}
MG_LORA_ADAPTER_MESSAGES_TIMEOUT: ${MG_LORA_ADAPTER_MESSAGES_TIMEOUT}
MG_LORA_ADAPTER_EVENT_CONSUMER: ${MG_LORA_ADAPTER_EVENT_CONSUMER}
MG_LORA_ADAPTER_HTTP_HOST: ${MG_LORA_ADAPTER_HTTP_HOST}
MG_LORA_ADAPTER_HTTP_PORT: ${MG_LORA_ADAPTER_HTTP_PORT}
MG_LORA_ADAPTER_HTTP_SERVER_CERT: ${MG_LORA_ADAPTER_HTTP_SERVER_CERT}
MG_LORA_ADAPTER_HTTP_SERVER_KEY: ${MG_LORA_ADAPTER_HTTP_SERVER_KEY}
MG_LORA_ADAPTER_ROUTE_MAP_URL: ${MG_LORA_ADAPTER_ROUTE_MAP_URL}
MG_ES_URL: ${MG_ES_URL}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_LORA_ADAPTER_INSTANCE_ID: ${MG_LORA_ADAPTER_INSTANCE_ID}
ports:
- ${MG_LORA_ADAPTER_HTTP_PORT}:${MG_LORA_ADAPTER_HTTP_PORT}
networks:
- magistrala-base-net
@@ -1,76 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional MongoDB-reader service
# for Magistrala platform. Since these are optional, this file is dependent of docker-compose file
# from <project_root>/docker. In order to run this service, execute command:
# docker compose -f docker/docker-compose.yml -f docker/addons/mongodb-reader/docker-compose.yml up
# from project root. MongoDB service is defined in docker/addons/mongodb-writer/docker-compose.yml.
networks:
magistrala-base-net:
services:
mongodb-reader:
image: magistrala/mongodb-reader:${MG_RELEASE_TAG}
container_name: magistrala-mongodb-reader
restart: on-failure
environment:
MG_MONGO_READER_LOG_LEVEL: ${MG_MONGO_READER_LOG_LEVEL}
MG_MONGO_READER_HTTP_HOST: ${MG_MONGO_READER_HTTP_HOST}
MG_MONGO_READER_HTTP_PORT: ${MG_MONGO_READER_HTTP_PORT}
MG_MONGO_READER_HTTP_SERVER_CERT: ${MG_MONGO_READER_HTTP_SERVER_CERT}
MG_MONGO_READER_HTTP_SERVER_KEY: ${MG_MONGO_READER_HTTP_SERVER_KEY}
MG_MONGO_HOST: ${MG_MONGO_HOST}
MG_MONGO_PORT: ${MG_MONGO_PORT}
MG_MONGO_NAME: ${MG_MONGO_NAME}
MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL}
MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT}
MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt}
MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key}
MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_MONGO_READER_INSTANCE_ID: ${MG_MONGO_READER_INSTANCE_ID}
ports:
- ${MG_MONGO_READER_HTTP_PORT}:${MG_MONGO_READER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ../../ssl/certs:/etc/ssl/certs
# Auth gRPC client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
# Things gRPC mTLS client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
-19
View File
@@ -1,19 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# To listen all messsage broker subjects use default value "channels.>".
# To subscribe to specific subjects use values starting by "channels." and
# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]).
[subscriber]
subjects = ["channels.>"]
[transformer]
# SenML or JSON
format = "senml"
# Used if format is SenML
content_type = "application/senml+json"
# Used as timestamp fields if format is JSON
time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"},
{ field_name = "millis_key", field_format = "unix_ms", location = "UTC"},
{ field_name = "micros_key", field_format = "unix_us", location = "UTC"},
{ field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}]
@@ -1,59 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional MongoDB and MongoDB-writer services
# for Magistrala platform. Since these are optional, this file is dependent of docker-compose file
# from <project_root>/docker. In order to run these services, execute command:
# docker compose -f docker/docker-compose.yml -f docker/addons/mongodb-writer/docker-compose.yml up
# from project root. MongoDB default port (27017) is exposed, so you can use various tools for database
# inspection and data visualization.
networks:
magistrala-base-net:
volumes:
magistrala-mongodb-db-volume:
magistrala-mongodb-configdb-volume:
services:
mongodb:
image: mongo:7.0.8
container_name: magistrala-mongodb
restart: on-failure
environment:
MONGO_INITDB_DATABASE: ${MG_MONGO_NAME}
ports:
- ${MG_MONGO_PORT}:${MG_MONGO_PORT}
networks:
- magistrala-base-net
volumes:
- magistrala-mongodb-db-volume:/data/db
- magistrala-mongodb-configdb-volume:/data/configdb
mongodb-writer:
image: magistrala/mongodb-writer:${MG_RELEASE_TAG}
container_name: magistrala-mongodb-writer
depends_on:
- mongodb
restart: on-failure
environment:
MG_MONGO_WRITER_LOG_LEVEL: ${MG_MONGO_WRITER_LOG_LEVEL}
MG_MONGO_WRITER_CONFIG_PATH: ${MG_MONGO_WRITER_CONFIG_PATH}
MG_MONGO_WRITER_HTTP_HOST: ${MG_MONGO_WRITER_HTTP_HOST}
MG_MONGO_WRITER_HTTP_PORT: ${MG_MONGO_WRITER_HTTP_PORT}
MG_MONGO_WRITER_HTTP_SERVER_CERT: ${MG_MONGO_WRITER_HTTP_SERVER_CERT}
MG_MONGO_WRITER_HTTP_SERVER_KEY: ${MG_MONGO_WRITER_HTTP_SERVER_KEY}
MG_MONGO_HOST: ${MG_MONGO_HOST}
MG_MONGO_PORT: ${MG_MONGO_PORT}
MG_MONGO_NAME: ${MG_MONGO_NAME}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_MONGO_WRITER_INSTANCE_ID: ${MG_MONGO_WRITER_INSTANCE_ID}
ports:
- ${MG_MONGO_WRITER_HTTP_PORT}:${MG_MONGO_WRITER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./config.toml:/config.toml
@@ -1,49 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional opcua-adapter and opcua-redis services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
volumes:
magistrala-opcua-adapter-volume:
magistrala-opcua-redis-volume:
services:
opcua-redis:
image: redis:7.2.4-alpine
container_name: magistrala-opcua-redis
restart: on-failure
networks:
- magistrala-base-net
volumes:
- magistrala-opcua-redis-volume:/data
opcua-adapter:
image: magistrala/opcua:${MG_RELEASE_TAG}
container_name: magistrala-opcua
restart: on-failure
environment:
MG_OPCUA_ADAPTER_LOG_LEVEL: ${MG_OPCUA_ADAPTER_LOG_LEVEL}
MG_OPCUA_ADAPTER_EVENT_CONSUMER: ${MG_OPCUA_ADAPTER_EVENT_CONSUMER}
MG_OPCUA_ADAPTER_HTTP_HOST: ${MG_OPCUA_ADAPTER_HTTP_HOST}
MG_OPCUA_ADAPTER_HTTP_PORT: ${MG_OPCUA_ADAPTER_HTTP_PORT}
MG_OPCUA_ADAPTER_HTTP_SERVER_CERT: ${MG_OPCUA_ADAPTER_HTTP_SERVER_CERT}
MG_OPCUA_ADAPTER_HTTP_SERVER_KEY: ${MG_OPCUA_ADAPTER_HTTP_SERVER_KEY}
MG_ES_URL: ${MG_ES_URL}
MG_OPCUA_ADAPTER_ROUTE_MAP_URL: ${MG_OPCUA_ADAPTER_ROUTE_MAP_URL}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_OPCUA_ADAPTER_INSTANCE_ID: ${MG_OPCUA_ADAPTER_INSTANCE_ID}
ports:
- ${MG_OPCUA_ADAPTER_HTTP_PORT}:${MG_OPCUA_ADAPTER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- magistrala-opcua-adapter-volume:/store
-8
View File
@@ -1,8 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# To listen all messsage broker subjects use default value "channels.>".
# To subscribe to specific subjects use values starting by "channels." and
# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]).
[subscriber]
subjects = ["channels.>"]
@@ -1,91 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional InfluxDB and InfluxDB-writer services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
volumes:
magistrala-smpp-notifier-volume:
services:
smpp-notifier-db:
image: postgres:16.2-alpine
container_name: magistrala-smpp-notifier-db
restart: on-failure
environment:
POSTGRES_USER: ${MG_SMPP_NOTIFIER_DB_USER}
POSTGRES_PASSWORD: ${MG_SMPP_NOTIFIER_DB_PASS}
POSTGRES_DB: ${MG_SMPP_NOTIFIER_DB_NAME}
networks:
- magistrala-base-net
volumes:
- magistrala-smpp-notifier-volume:/var/lib/postgresql/data
smpp-notifier:
image: magistrala/smpp-notifier:latest
container_name: magistrala-smpp-notifier
depends_on:
- smpp-notifier-db
restart: on-failure
environment:
MG_SMPP_NOTIFIER_LOG_LEVEL: ${MG_SMPP_NOTIFIER_LOG_LEVEL}
MG_SMPP_NOTIFIER_FROM_ADDR: ${MG_SMPP_NOTIFIER_FROM_ADDR}]
MG_SMPP_NOTIFIER_CONFIG_PATH: ${MG_SMPP_NOTIFIER_CONFIG_PATH}
MG_SMPP_NOTIFIER_HTTP_HOST: ${MG_SMPP_NOTIFIER_HTTP_HOST}
MG_SMPP_NOTIFIER_HTTP_PORT: ${MG_SMPP_NOTIFIER_HTTP_PORT}
MG_SMPP_NOTIFIER_HTTP_SERVER_CERT: ${MG_SMPP_NOTIFIER_HTTP_SERVER_CERT}
MG_SMPP_NOTIFIER_HTTP_SERVER_KEY: ${MG_SMPP_NOTIFIER_HTTP_SERVER_KEY}
MG_SMPP_NOTIFIER_DB_HOST: ${MG_SMPP_NOTIFIER_DB_HOST}
MG_SMPP_NOTIFIER_DB_PORT: ${MG_SMPP_NOTIFIER_DB_PORT}
MG_SMPP_NOTIFIER_DB_USER: ${MG_SMPP_NOTIFIER_DB_USER}
MG_SMPP_NOTIFIER_DB_PASS: ${MG_SMPP_NOTIFIER_DB_PASS}
MG_SMPP_NOTIFIER_DB_NAME: ${MG_SMPP_NOTIFIER_DB_NAME}
MG_SMPP_NOTIFIER_DB_SSL_MODE: ${MG_SMPP_NOTIFIER_DB_SSL_MODE}
MG_SMPP_NOTIFIER_DB_SSL_CERT: ${MG_SMPP_NOTIFIER_DB_SSL_CERT}
MG_SMPP_NOTIFIER_DB_SSL_KEY: ${MG_SMPP_NOTIFIER_DB_SSL_KEY}
MG_SMPP_NOTIFIER_DB_SSL_ROOT_CERT: ${MG_SMPP_NOTIFIER_DB_SSL_ROOT_CERT}
MG_SMPP_ADDRESS: ${MG_SMPP_ADDRESS}
MG_SMPP_USERNAME: ${MG_SMPP_USERNAME}
MG_SMPP_PASSWORD: ${MG_SMPP_PASSWORD}
MG_SMPP_SYSTEM_TYPE: ${MG_SMPP_SYSTEM_TYPE}
MG_SMPP_SRC_ADDR_TON: ${MG_SMPP_SRC_ADDR_TON}
MG_SMPP_SRC_ADDR_NPI: ${MG_SMPP_SRC_ADDR_NPI}
MG_SMPP_DST_ADDR_TON: ${MG_SMPP_DST_ADDR_TON}
MG_SMPP_DST_ADDR_NPI: ${MG_SMPP_DST_ADDR_NPI}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_SMPP_NOTIFIER_INSTANCE_ID: ${MG_SMPP_NOTIFIER_INSTANCE_ID}
ports:
- ${MG_SMPP_NOTIFIER_HTTP_PORT}:${MG_SMPP_NOTIFIER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./config.toml:/config.toml
# Auth gRPC client certificates
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
-8
View File
@@ -1,8 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# To listen all messsage broker subjects use default value "channels.>".
# To subscribe to specific subjects use values starting by "channels." and
# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]).
[subscriber]
subjects = ["channels.>"]
@@ -1,90 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional InfluxDB and InfluxDB-writer services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
volumes:
magistrala-smtp-notifier-volume:
services:
smtp-notifier-db:
image: postgres:16.2-alpine
container_name: magistrala-smtp-notifier-db
restart: on-failure
environment:
POSTGRES_USER: ${MG_SMTP_NOTIFIER_DB_USER}
POSTGRES_PASSWORD: ${MG_SMTP_NOTIFIER_DB_PASS}
POSTGRES_DB: ${MG_SMTP_NOTIFIER_DB_NAME}
networks:
- magistrala-base-net
volumes:
- magistrala-smtp-notifier-volume:/var/lib/postgresql/datab
smtp-notifier:
image: magistrala/smtp-notifier:latest
container_name: magistrala-smtp-notifier
depends_on:
- smtp-notifier-db
restart: on-failure
environment:
MG_SMTP_NOTIFIER_LOG_LEVEL: ${MG_SMTP_NOTIFIER_LOG_LEVEL}
MG_SMTP_NOTIFIER_FROM_ADDR: ${MG_SMTP_NOTIFIER_FROM_ADDR}]
MG_SMTP_NOTIFIER_CONFIG_PATH: ${MG_SMTP_NOTIFIER_CONFIG_PATH}
MG_SMTP_NOTIFIER_HTTP_HOST: ${MG_SMTP_NOTIFIER_HTTP_HOST}
MG_SMTP_NOTIFIER_HTTP_PORT: ${MG_SMTP_NOTIFIER_HTTP_PORT}
MG_SMTP_NOTIFIER_HTTP_SERVER_CERT: ${MG_SMTP_NOTIFIER_HTTP_SERVER_CERT}
MG_SMTP_NOTIFIER_HTTP_SERVER_KEY: ${MG_SMTP_NOTIFIER_HTTP_SERVER_KEY}
MG_SMTP_NOTIFIER_DB_HOST: ${MG_SMTP_NOTIFIER_DB_HOST}
MG_SMTP_NOTIFIER_DB_PORT: ${MG_SMTP_NOTIFIER_DB_PORT}
MG_SMTP_NOTIFIER_DB_USER: ${MG_SMTP_NOTIFIER_DB_USER}
MG_SMTP_NOTIFIER_DB_PASS: ${MG_SMTP_NOTIFIER_DB_PASS}
MG_SMTP_NOTIFIER_DB_NAME: ${MG_SMTP_NOTIFIER_DB_NAME}
MG_SMTP_NOTIFIER_DB_SSL_MODE: ${MG_SMTP_NOTIFIER_DB_SSL_MODE}
MG_SMTP_NOTIFIER_DB_SSL_CERT: ${MG_SMTP_NOTIFIER_DB_SSL_CERT}
MG_SMTP_NOTIFIER_DB_SSL_KEY: ${MG_SMTP_NOTIFIER_DB_SSL_KEY}
MG_SMTP_NOTIFIER_DB_SSL_ROOT_CERT: ${MG_SMTP_NOTIFIER_DB_SSL_ROOT_CERT}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_EMAIL_USERNAME: ${MG_EMAIL_USERNAME}
MG_EMAIL_PASSWORD: ${MG_EMAIL_PASSWORD}
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
MG_EMAIL_FROM_ADDRESS: ${MG_EMAIL_FROM_ADDRESS}
MG_EMAIL_FROM_NAME: ${MG_EMAIL_FROM_NAME}
MG_EMAIL_TEMPLATE: ${MG_SMTP_NOTIFIER_EMAIL_TEMPLATE}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_SMTP_NOTIFIER_INSTANCE_ID: ${MG_SMTP_NOTIFIER_INSTANCE_ID}
ports:
- ${MG_SMTP_NOTIFIER_HTTP_PORT}:${MG_SMTP_NOTIFIER_HTTP_PORT}
networks:
- magistrala-base-net
volumes:
- ./config.toml:/config.toml
- ../../templates/${MG_SMTP_NOTIFIER_EMAIL_TEMPLATE}:/${MG_SMTP_NOTIFIER_EMAIL_TEMPLATE}
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
-91
View File
@@ -1,91 +0,0 @@
# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0
# This docker-compose file contains optional opcua-adapter and opcua-redis services
# for the Magistrala platform. Since this services are optional, this file is dependent on the
# docker-compose.yml file from <project_root>/docker/. In order to run these services,
# core services, as well as the network from the core composition, should be already running.
networks:
magistrala-base-net:
volumes:
magistrala-twins-db-volume:
magistrala-twins-db-configdb-volume:
services:
twins-redis:
image: redis:7.2.4-alpine
container_name: magistrala-twins-redis
restart: on-failure
networks:
- magistrala-base-net
twins-db:
image: mongo:bionic
command: mongod --port ${MG_TWINS_DB_PORT}
container_name: magistrala-twins-db
restart: on-failure
environment:
MONGO_INITDB_DATABASE: ${MG_TWINS_DB_NAME}
ports:
- ${MG_TWINS_DB_PORT}:${MG_TWINS_DB_PORT}
networks:
magistrala-base-net:
volumes:
- magistrala-twins-db-volume:/data/db
- magistrala-twins-db-configdb-volume:/data/configdb
twins:
image: magistrala/twins:${MG_RELEASE_TAG}
container_name: magistrala-twins
restart: on-failure
environment:
MG_TWINS_LOG_LEVEL: ${MG_TWINS_LOG_LEVEL}
MG_TWINS_STANDALONE_ID: ${MG_TWINS_STANDALONE_ID}
MG_TWINS_STANDALONE_TOKEN: ${MG_TWINS_STANDALONE_TOKEN}
MG_TWINS_CHANNEL_ID: ${MG_TWINS_CHANNEL_ID}
MG_TWINS_HTTP_HOST: ${MG_TWINS_HTTP_HOST}
MG_TWINS_HTTP_PORT: ${MG_TWINS_HTTP_PORT}
MG_TWINS_HTTP_SERVER_CERT: ${MG_TWINS_HTTP_SERVER_CERT}
MG_TWINS_HTTP_SERVER_KEY: ${MG_TWINS_HTTP_SERVER_KEY}
MG_TWINS_CACHE_URL: ${MG_TWINS_CACHE_URL}
MG_ES_URL: ${MG_ES_URL}
MG_THINGS_STANDALONE_ID: ${MG_THINGS_STANDALONE_ID}
MG_THINGS_STANDALONE_TOKEN: ${MG_THINGS_STANDALONE_TOKEN}
MG_TWINS_DB_HOST: ${MG_TWINS_DB_HOST}
MG_TWINS_DB_PORT: ${MG_TWINS_DB_PORT}
MG_TWINS_DB_NAME: ${MG_TWINS_DB_NAME}
MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL}
MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT}
MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL}
MG_JAEGER_URL: ${MG_JAEGER_URL}
MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO}
MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY}
MG_TWINS_INSTANCE_ID: ${MG_TWINS_INSTANCE_ID}
ports:
- ${MG_TWINS_HTTP_PORT}:${MG_TWINS_HTTP_PORT}
networks:
magistrala-base-net:
depends_on:
- twins-db
- twins-redis
volumes:
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key}
target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key}
bind:
create_host_path: true
- type: bind
source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca}
target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
bind:
create_host_path: true
-18
View File
@@ -13,19 +13,15 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/fatih/color v1.17.0
github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba
github.com/go-chi/chi/v5 v5.1.0
github.com/go-kit/kit v0.13.0
github.com/go-redis/redis/v8 v8.11.5
github.com/gocql/gocql v1.6.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gookit/color v1.5.4
github.com/gopcua/opcua v0.1.6
github.com/gorilla/websocket v1.5.3
github.com/hashicorp/vault/api v1.14.0
github.com/hashicorp/vault/api/auth/approle v0.7.0
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f
github.com/influxdata/influxdb-client-go/v2 v2.13.0
github.com/ivanpirog/coloredcobra v1.0.1
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jackc/pgtype v1.14.3
@@ -44,7 +40,6 @@ require (
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.mongodb.org/mongo-driver v1.15.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0
go.opentelemetry.io/otel v1.28.0
@@ -70,9 +65,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-hostpool v0.1.0 // indirect
github.com/caarlos0/env/v11 v11.0.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
@@ -98,12 +91,9 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -114,7 +104,6 @@ require (
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
@@ -132,11 +121,9 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/onsi/gomega v1.33.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
@@ -165,14 +152,10 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
@@ -185,7 +168,6 @@ require (
golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
-55
View File
@@ -15,7 +15,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/absmach/callhome v0.14.0 h1:zB4tIZJ1YUmZ1VGHFPfMA/Lo6/Mv19y2dvoOiXj2BWs=
@@ -24,8 +23,6 @@ github.com/absmach/mproxy v0.4.3-0.20240430090627-27dad4c91c6c h1:wGtfVk3knDUsrU
github.com/absmach/mproxy v0.4.3-0.20240430090627-27dad4c91c6c/go.mod h1:Nevip6o8u5Zx7l3LTtN8BwlCI5h5KpsnI9YnAxF5RT8=
github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE=
github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/authzed/authzed-go v0.13.0 h1:wX/YpL/CW5RZLqMUKor2z1knJhCh7DjEu1qW3HVsp/w=
github.com/authzed/authzed-go v0.13.0/go.mod h1:i62WRU5roWUGzYPxuZzBi3/FBUNvGWYEjyI/m3xWMQc=
github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw=
@@ -33,12 +30,6 @@ github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b/go.mod h1:s3qC7V7
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0=
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/caarlos0/env/v11 v11.0.0 h1:ZIlkOjuL3xoZS0kmUJlF74j2Qj8GMOq3CDLX/Viak8Q=
@@ -59,7 +50,6 @@ github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7b
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -96,8 +86,6 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba h1:vBqABUa2HUSc6tj22Tw+ZMVGHuBzKtljM38kbRanmrM=
github.com/fiorix/go-smpp v0.0.0-20210403173735-2894b96e70ba/go.mod h1:VfKFK7fGeCP81xEhbrOqUEh45n73Yy6jaPWwTVbxprI=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
@@ -133,8 +121,6 @@ github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU=
github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -147,21 +133,14 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gopcua/opcua v0.1.6 h1:B9SVRKQGzcWcwP2QPYN93Uku32+3wL+v5cgzBxE6V5I=
github.com/gopcua/opcua v0.1.6/go.mod h1:INwnDoRxmNWAt7+tzqxuGqQkSF2c1C69VAL0c2q6AcY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -170,8 +149,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -202,10 +179,6 @@ github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:p
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=
github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4=
github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
@@ -269,7 +242,6 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas=
github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -328,8 +300,6 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
@@ -338,8 +308,6 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
@@ -355,8 +323,6 @@ github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
@@ -399,7 +365,6 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos=
github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@@ -414,7 +379,6 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -436,7 +400,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -458,15 +421,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -476,14 +432,10 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
@@ -526,10 +478,8 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -591,7 +541,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -627,13 +576,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -691,8 +638,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-72
View File
@@ -1,72 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cassandra
import (
"github.com/absmach/magistrala/pkg/errors"
"github.com/caarlos0/env/v10"
"github.com/gocql/gocql"
)
var (
errConfig = errors.New("failed to load Cassandra configuration")
errConnect = errors.New("failed to connect to Cassandra database")
errInit = errors.New("failed to execute initialization query in Cassandra ")
)
// Config contains Cassandra DB specific parameters.
type Config struct {
Hosts []string `env:"CLUSTER" envDefault:"127.0.0.1" envSeparator:","`
Keyspace string `env:"KEYSPACE" envDefault:"magistrala"`
User string `env:"USER" envDefault:""`
Pass string `env:"PASS" envDefault:""`
Port int `env:"PORT" envDefault:"9042"`
}
// Setup load configuration from environment and creates new cassandra connection.
func Setup(envPrefix string) (*gocql.Session, error) {
return SetupDB(envPrefix, "")
}
// SetupDB load configuration from environment,
// creates new cassandra connection and executes
// the initial query in database.
func SetupDB(envPrefix, initQuery string) (*gocql.Session, error) {
cfg := Config{}
if err := env.ParseWithOptions(&cfg, env.Options{Prefix: envPrefix}); err != nil {
return nil, errors.Wrap(errConfig, err)
}
cs, err := Connect(cfg)
if err != nil {
return nil, err
}
if initQuery != "" {
if err := InitDB(cs, initQuery); err != nil {
return nil, errors.Wrap(errInit, err)
}
}
return cs, nil
}
// Connect establishes connection to the Cassandra cluster.
func Connect(cfg Config) (*gocql.Session, error) {
cluster := gocql.NewCluster(cfg.Hosts...)
cluster.Keyspace = cfg.Keyspace
cluster.Consistency = gocql.Quorum
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: cfg.User,
Password: cfg.Pass,
}
cluster.Port = cfg.Port
cassSess, err := cluster.CreateSession()
if err != nil {
return nil, errors.Wrap(errConnect, err)
}
return cassSess, nil
}
func InitDB(cs *gocql.Session, query string) error {
return cs.Query(query).Exec()
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package cassandra contains the domain concept definitions needed to support
// Magistrala Cassandra database functionality.
//
// It provides the abstraction of the Cassandra database service, which is used
// to configure, setup and connect to the Cassandra database.
package cassandra
-9
View File
@@ -1,9 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package influxdb contains the domain concept definitions needed to support
// Magistrala InfluxDB database functionality.
//
// It provides the abstraction of the InfluxDB database service, which is used
// to configure, setup and connect to the InfluxDB database.
package influxdb
-57
View File
@@ -1,57 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package influxdb
import (
"context"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/caarlos0/env/v10"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)
var (
errConnect = errors.New("failed to create InfluxDB client")
errConfig = errors.New("failed to load InfluxDB client configuration from environment variable")
)
type Config struct {
Protocol string `env:"PROTOCOL" envDefault:"http"`
Host string `env:"HOST" envDefault:"localhost"`
Port string `env:"PORT" envDefault:"8086"`
Username string `env:"ADMIN_USER" envDefault:"magistrala"`
Password string `env:"ADMIN_PASSWORD" envDefault:"magistrala"`
DBName string `env:"NAME" envDefault:"magistrala"`
Bucket string `env:"BUCKET" envDefault:"magistrala-bucket"`
Org string `env:"ORG" envDefault:"magistrala"`
Token string `env:"TOKEN" envDefault:"magistrala-token"`
DBUrl string `env:"DBURL" envDefault:""`
UserAgent string `env:"USER_AGENT" envDefault:"InfluxDBClient"`
Timeout time.Duration `env:"TIMEOUT"` // Influxdb client configuration by default has no timeout duration , this field will not have a fallback default timeout duration. Reference: https://pkg.go.dev/github.com/influxdata/influxdb@v1.10.0/client/v2#HTTPConfig
InsecureSkipVerify bool `env:"INSECURE_SKIP_VERIFY" envDefault:"false"`
}
// Setup load configuration from environment variable, create InfluxDB client and connect to InfluxDB server.
func Setup(ctx context.Context, envPrefix string) (influxdb2.Client, error) {
cfg := Config{}
if err := env.ParseWithOptions(&cfg, env.Options{Prefix: envPrefix}); err != nil {
return nil, errors.Wrap(errConfig, err)
}
return Connect(ctx, cfg)
}
// Connect create InfluxDB client and connect to InfluxDB server.
func Connect(ctx context.Context, config Config) (influxdb2.Client, error) {
client := influxdb2.NewClientWithOptions(config.DBUrl, config.Token,
influxdb2.DefaultOptions().
SetUseGZip(true).
SetFlushInterval(100))
ctx, cancel := context.WithTimeout(ctx, config.Timeout)
defer cancel()
if _, err := client.Ready(ctx); err != nil {
return nil, errors.Wrap(errConnect, err)
}
return client, nil
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package mongodb contains the domain concept definitions needed to support
// Magistrala Mongo database functionality.
//
// It provides the abstraction of the Mongo database service, which is used
// to configure, setup and connect to the Mongo database.
package mongodb
-51
View File
@@ -1,51 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mongodb
import (
"context"
"fmt"
"github.com/absmach/magistrala/pkg/errors"
"github.com/caarlos0/env/v10"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
errConfig = errors.New("failed to load mongodb configuration")
errConnect = errors.New("failed to connect to mongodb server")
)
// Config defines the options that are used when connecting to a MongoDB instance.
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port string `env:"PORT" envDefault:"27017"`
Name string `env:"NAME" envDefault:"messages"`
}
// Connect creates a connection to the MongoDB instance.
func Connect(cfg Config) (*mongo.Database, error) {
addr := fmt.Sprintf("mongodb://%s:%s", cfg.Host, cfg.Port)
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(addr))
if err != nil {
return nil, errors.Wrap(errConnect, err)
}
db := client.Database(cfg.Name)
return db, nil
}
// Setup load configuration from environment, create new MongoDB client and connect to MongoDB server.
func Setup(envPrefix string) (*mongo.Database, error) {
cfg := Config{}
if err := env.ParseWithOptions(&cfg, env.Options{Prefix: envPrefix}); err != nil {
return nil, errors.Wrap(errConfig, err)
}
db, err := Connect(cfg)
if err != nil {
return nil, err
}
return db, nil
}
-87
View File
@@ -1,87 +0,0 @@
# LoRa Adapter
Adapter between Magistrala IoT system and [LoRa Server](https://github.com/brocaar/chirpstack-network-server).
This adapter sits between Magistrala and LoRa Server and just forwards the messages from one system to another via MQTT protocol, using the adequate MQTT topics and in the good message format (JSON and SenML), i.e. respecting the APIs of both systems.
LoRa Server is used for connectivity layer and data is pushed via this adapter service to Magistrala, where it is persisted and routed to other protocols via Magistrala multi-protocol message broker. Magistrala adds user accounts, application management and security in order to obtain the overall end-to-end LoRa solution.
## 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 |
| -------------------------------- | --------------------------------------------------------- | ----------------------------------- |
| MG_LORA_ADAPTER_LOG_LEVEL | Log level for the LoRa Adapter (debug, info, warn, error) | info |
| MG_LORA_ADAPTER_HTTP_HOST | Service LoRa host | "" |
| MG_LORA_ADAPTER_HTTP_PORT | Service LoRa port | 9017 |
| MG_LORA_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" |
| MG_LORA_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" |
| MG_LORA_ADAPTER_MESSAGES_URL | LoRa adapter MQTT broker URL | tcp://localhost:1883 |
| MG_LORA_ADAPTER_MESSAGES_TOPIC | LoRa adapter MQTT subscriber Topic | application/+/device/+/event/up |
| MG_LORA_ADAPTER_MESSAGES_USER | LoRa adapter MQTT subscriber Username | "" |
| MG_LORA_ADAPTER_MESSAGES_PASS | LoRa adapter MQTT subscriber Password | "" |
| MG_LORA_ADAPTER_MESSAGES_TIMEOUT | LoRa adapter MQTT subscriber Timeout | 30s |
| MG_LORA_ADAPTER_ROUTE_MAP_URL | Route-map database URL | redis://localhost:6379 |
| MG_ES_URL | Event source URL | <nats://localhost:4222> |
| MG_LORA_ADAPTER_EVENT_CONSUMER | Service event consumer name | lora-adapter |
| MG_MESSAGE_BROKER_URL | Message broker instance URL | <nats://localhost:4222> |
| MG_JAEGER_URL | Jaeger server URL | <http://localhost:14268/api/traces> |
| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_LORA_ADAPTER_INSTANCE_ID | Service instance ID | "" |
## Deployment
The service itself is distributed as Docker container. Check the [`lora-adapter`](https://github.com/absmach/magistrala/blob/main/docker/addons/lora-adapter/docker-compose.yml) service section in docker-compose file to see how service is deployed.
Running this service outside of container requires working instance of the message broker service, LoRa server, things service and Jaeger server.
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
git clone https://github.com/absmach/magistrala
cd magistrala
# compile the lora adapter
make lora
# copy binary to bin
make install
# set the environment variables and run the service
MG_LORA_ADAPTER_LOG_LEVEL=info \
MG_LORA_ADAPTER_HTTP_HOST=localhost \
MG_LORA_ADAPTER_HTTP_PORT=9017 \
MG_LORA_ADAPTER_HTTP_SERVER_CERT="" \
MG_LORA_ADAPTER_HTTP_SERVER_KEY="" \
MG_LORA_ADAPTER_MESSAGES_URL=tcp://localhost:1883 \
MG_LORA_ADAPTER_MESSAGES_TOPIC=application/+/device/+/event/up \
MG_LORA_ADAPTER_MESSAGES_USER="" \
MG_LORA_ADAPTER_MESSAGES_PASS="" \
MG_LORA_ADAPTER_MESSAGES_TIMEOUT=30s \
MG_LORA_ADAPTER_ROUTE_MAP_URL=redis://localhost:6379 \
MG_ES_URL=nats://localhost:4222 \
MG_LORA_ADAPTER_EVENT_CONSUMER=lora-adapter \
MG_MESSAGE_BROKER_URL=nats://localhost:4222 \
MG_JAEGER_URL=http://localhost:14268/api/traces \
MG_JAEGER_TRACE_RATIO=1.0 \
MG_SEND_TELEMETRY=true \
MG_LORA_ADAPTER_INSTANCE_ID="" \
$GOBIN/magistrala-lora
```
Setting `MG_LORA_ADAPTER_HTTP_SERVER_CERT` and `MG_LORA_ADAPTER_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
### Using docker-compose
This service can be deployed using docker containers. Docker compose file is available in `<project_root>/docker/addons/lora-adapter/docker-compose.yml`. In order to run Magistrala lora-adapter, execute the following command:
```bash
docker compose -f docker/addons/lora-adapter/docker-compose.yml up -d
```
## Usage
For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.magistrala.abstractmachines.fr/lora).
-179
View File
@@ -1,179 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package lora
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/absmach/magistrala/pkg/messaging"
)
const protocol = "lora"
var (
// ErrMalformedMessage indicates malformed LoRa message.
ErrMalformedMessage = errors.New("malformed message received")
// ErrNotFoundDev indicates a non-existent route map for a device EUI.
ErrNotFoundDev = errors.New("route map not found for this device EUI")
// ErrNotFoundApp indicates a non-existent route map for an application ID.
ErrNotFoundApp = errors.New("route map not found for this application ID")
// ErrNotConnected indicates a non-existent route map for a connection.
ErrNotConnected = errors.New("route map not found for this connection")
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// CreateThing creates thingID:devEUI route-map
CreateThing(ctx context.Context, thingID, devEUI string) error
// UpdateThing updates thingID:devEUI route-map
UpdateThing(ctx context.Context, thingID, devEUI string) error
// RemoveThing removes thingID:devEUI route-map
RemoveThing(ctx context.Context, thingID string) error
// CreateChannel creates channelID:appID route-map
CreateChannel(ctx context.Context, chanID, appID string) error
// UpdateChannel updates channelID:appID route-map
UpdateChannel(ctx context.Context, chanID, appID string) error
// RemoveChannel removes channelID:appID route-map
RemoveChannel(ctx context.Context, chanID string) error
// ConnectThing creates thingID:channelID route-map
ConnectThing(ctx context.Context, chanID, thingID string) error
// DisconnectThing removes thingID:channelID route-map
DisconnectThing(ctx context.Context, chanID, thingID string) error
// Publish forwards messages from the LoRa MQTT broker to Magistrala Message Broker
Publish(ctx context.Context, msg *Message) error
}
var _ Service = (*adapterService)(nil)
type adapterService struct {
publisher messaging.Publisher
thingsRM RouteMapRepository
channelsRM RouteMapRepository
connectRM RouteMapRepository
}
// New instantiates the LoRa adapter implementation.
func New(publisher messaging.Publisher, thingsRM, channelsRM, connectRM RouteMapRepository) Service {
return &adapterService{
publisher: publisher,
thingsRM: thingsRM,
channelsRM: channelsRM,
connectRM: connectRM,
}
}
// Publish forwards messages from Lora MQTT broker to Magistrala Message broker.
func (as *adapterService) Publish(ctx context.Context, m *Message) error {
// Get route map of lora application
thingID, err := as.thingsRM.Get(ctx, m.DevEUI)
if err != nil {
return ErrNotFoundDev
}
// Get route map of lora application
chanID, err := as.channelsRM.Get(ctx, m.ApplicationID)
if err != nil {
return ErrNotFoundApp
}
c := fmt.Sprintf("%s:%s", chanID, thingID)
if _, err := as.connectRM.Get(ctx, c); err != nil {
return ErrNotConnected
}
// Use the SenML message decoded on LoRa Server application if
// field Object isn't empty. Otherwise, decode standard field Data.
var payload []byte
switch m.Object {
case nil:
payload, err = base64.StdEncoding.DecodeString(m.Data)
if err != nil {
return ErrMalformedMessage
}
default:
jo, err := json.Marshal(m.Object)
if err != nil {
return err
}
payload = jo
}
// Publish on Magistrala Message broker
msg := messaging.Message{
Publisher: thingID,
Protocol: protocol,
Channel: chanID,
Payload: payload,
Created: time.Now().UnixNano(),
}
return as.publisher.Publish(ctx, msg.Channel, &msg)
}
func (as *adapterService) CreateThing(ctx context.Context, thingID, devEUI string) error {
return as.thingsRM.Save(ctx, thingID, devEUI)
}
func (as *adapterService) UpdateThing(ctx context.Context, thingID, devEUI string) error {
return as.thingsRM.Save(ctx, thingID, devEUI)
}
func (as *adapterService) RemoveThing(ctx context.Context, thingID string) error {
return as.thingsRM.Remove(ctx, thingID)
}
func (as *adapterService) CreateChannel(ctx context.Context, chanID, appID string) error {
return as.channelsRM.Save(ctx, chanID, appID)
}
func (as *adapterService) UpdateChannel(ctx context.Context, chanID, appID string) error {
return as.channelsRM.Save(ctx, chanID, appID)
}
func (as *adapterService) RemoveChannel(ctx context.Context, chanID string) error {
return as.channelsRM.Remove(ctx, chanID)
}
func (as *adapterService) ConnectThing(ctx context.Context, chanID, thingID string) error {
if _, err := as.channelsRM.Get(ctx, chanID); err != nil {
return ErrNotFoundApp
}
if _, err := as.thingsRM.Get(ctx, thingID); err != nil {
return ErrNotFoundDev
}
c := fmt.Sprintf("%s:%s", chanID, thingID)
return as.connectRM.Save(ctx, c, c)
}
func (as *adapterService) DisconnectThing(ctx context.Context, chanID, thingID string) error {
if _, err := as.channelsRM.Get(ctx, chanID); err != nil {
return ErrNotFoundApp
}
if _, err := as.thingsRM.Get(ctx, thingID); err != nil {
return ErrNotFoundDev
}
c := fmt.Sprintf("%s:%s", chanID, thingID)
return as.connectRM.Remove(ctx, c)
}
-478
View File
@@ -1,478 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package lora_test
import (
"context"
"encoding/base64"
"fmt"
"testing"
"github.com/absmach/magistrala/lora"
"github.com/absmach/magistrala/lora/mocks"
"github.com/absmach/magistrala/pkg/errors"
pubmocks "github.com/absmach/magistrala/pkg/messaging/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const (
thingID = "thingID-1"
chanID = "chanID-1"
devEUI = "devEUI-1"
appID = "appID-1"
thingID2 = "thingID-2"
chanID2 = "chanID-2"
devEUI2 = "devEUI-2"
appID2 = "appID-2"
msg = `[{"bn":"msg-base-name","n":"temperature","v": 17},{"n":"humidity","v": 56}]`
invalid = "wrong"
)
var (
pub *pubmocks.PubSub
thingsRM, channelsRM, connsRM *mocks.RouteMapRepository
)
func newService() lora.Service {
pub = new(pubmocks.PubSub)
thingsRM = new(mocks.RouteMapRepository)
channelsRM = new(mocks.RouteMapRepository)
connsRM = new(mocks.RouteMapRepository)
return lora.New(pub, thingsRM, channelsRM, connsRM)
}
func TestPublish(t *testing.T) {
svc := newService()
msgBase64 := base64.StdEncoding.EncodeToString([]byte(msg))
cases := []struct {
desc string
err error
msg lora.Message
getThingErr error
getChannelErr error
connectionsErr error
publishErr error
}{
{
desc: "publish message with existing route-map and valid Data",
err: nil,
msg: lora.Message{
ApplicationID: appID,
DevEUI: devEUI,
Data: msgBase64,
},
getThingErr: nil,
getChannelErr: nil,
connectionsErr: nil,
publishErr: nil,
},
{
desc: "publish message with existing route-map and invalid Data",
err: lora.ErrMalformedMessage,
msg: lora.Message{
ApplicationID: appID,
DevEUI: devEUI,
Data: "wrong",
},
getThingErr: nil,
getChannelErr: nil,
connectionsErr: nil,
publishErr: errors.New("Failed publishing"),
},
{
desc: "publish message with non existing appID route-map",
err: lora.ErrNotFoundApp,
msg: lora.Message{
ApplicationID: "wrong",
DevEUI: devEUI,
},
getChannelErr: lora.ErrNotFoundApp,
},
{
desc: "publish message with non existing devEUI route-map",
err: lora.ErrNotFoundDev,
msg: lora.Message{
ApplicationID: appID,
DevEUI: "wrong",
},
getThingErr: lora.ErrNotFoundDev,
},
{
desc: "publish message with non existing connection route-map",
err: lora.ErrNotConnected,
msg: lora.Message{
ApplicationID: appID2,
DevEUI: devEUI2,
},
connectionsErr: lora.ErrNotConnected,
},
{
desc: "publish message with wrong Object",
err: errors.New("json: unsupported type: chan int"),
msg: lora.Message{
ApplicationID: appID2,
DevEUI: devEUI2,
Object: make(chan int),
},
},
{
desc: "publish message with valid Object",
err: nil,
msg: lora.Message{
ApplicationID: appID2,
DevEUI: devEUI2,
Object: map[string]interface{}{"key": "value"},
},
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Get", context.Background(), tc.msg.DevEUI).Return(tc.msg.DevEUI, tc.getThingErr)
repoCall1 := channelsRM.On("Get", context.Background(), tc.msg.ApplicationID).Return(tc.msg.ApplicationID, tc.getChannelErr)
repoCall2 := connsRM.On("Get", context.Background(), mock.Anything).Return("", tc.connectionsErr)
repoCall3 := pub.On("Publish", context.Background(), tc.msg.ApplicationID, mock.Anything).Return(tc.publishErr)
err := svc.Publish(context.Background(), &tc.msg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
repoCall3.Unset()
}
}
func TestCreateChannel(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ChanID string
AppID string
}{
{
desc: "create channel with valid data",
err: nil,
ChanID: chanID,
AppID: appID,
},
{
desc: "create channel with empty chanID",
err: lora.ErrNotFoundApp,
ChanID: "",
AppID: appID,
},
{
desc: "create channel with empty appID",
err: lora.ErrNotFoundApp,
ChanID: chanID,
AppID: "",
},
}
for _, tc := range cases {
repoCall := channelsRM.On("Save", context.Background(), tc.ChanID, tc.AppID).Return(tc.err)
err := svc.CreateChannel(context.Background(), tc.ChanID, tc.AppID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
func TestCreateThing(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ThingID string
DevEUI string
}{
{
desc: "create thing with valid data",
err: nil,
ThingID: thingID,
DevEUI: devEUI,
},
{
desc: "create thing with empty thingID",
err: lora.ErrNotFoundDev,
ThingID: "",
DevEUI: devEUI,
},
{
desc: "create thing with empty devEUI",
err: lora.ErrNotFoundDev,
ThingID: thingID,
DevEUI: "",
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Save", context.Background(), tc.ThingID, tc.DevEUI).Return(tc.err)
err := svc.CreateThing(context.Background(), tc.ThingID, tc.DevEUI)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
func TestConnectThing(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
channelID string
thingID string
getThingErr error
getChannelErr error
}{
{
desc: "connect thing with valid data",
err: nil,
channelID: chanID,
thingID: thingID,
getThingErr: nil,
getChannelErr: nil,
},
{
desc: "connect thing with non existing thing",
err: lora.ErrNotFoundDev,
channelID: chanID,
thingID: invalid,
getThingErr: lora.ErrNotFoundDev,
},
{
desc: "connect thing with non existing channel",
err: lora.ErrNotFoundApp,
channelID: invalid,
thingID: thingID,
getChannelErr: lora.ErrNotFoundApp,
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Get", context.Background(), tc.thingID).Return(devEUI, tc.getThingErr)
repoCall1 := channelsRM.On("Get", context.Background(), tc.channelID).Return(appID, tc.getChannelErr)
repoCall2 := connsRM.On("Save", context.Background(), mock.Anything, mock.Anything).Return(tc.err)
err := svc.ConnectThing(context.Background(), tc.channelID, tc.thingID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
}
}
func TestDisconnectThing(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
channelID string
thingID string
getThingErr error
getChannelErr error
}{
{
desc: "disconnect thing with valid data",
err: nil,
channelID: chanID,
thingID: thingID,
getThingErr: nil,
getChannelErr: nil,
},
{
desc: "disconnect thing with non existing thing ID",
err: lora.ErrNotFoundDev,
channelID: chanID,
thingID: invalid,
getThingErr: lora.ErrNotFoundDev,
},
{
desc: "disconnect thing with non existing channel",
err: lora.ErrNotFoundApp,
channelID: invalid,
thingID: thingID,
getChannelErr: lora.ErrNotFoundApp,
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Get", context.Background(), tc.thingID).Return(devEUI, tc.getThingErr)
repoCall1 := channelsRM.On("Get", context.Background(), tc.channelID).Return(appID, tc.getChannelErr)
repoCall2 := connsRM.On("Remove", context.Background(), mock.Anything).Return(tc.err)
err := svc.DisconnectThing(context.Background(), tc.channelID, tc.thingID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
}
}
func TestRemoveChannel(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ChanID string
}{
{
desc: "remove channel with valid data",
err: nil,
ChanID: chanID,
},
{
desc: "remove channel with non existing channel",
err: lora.ErrNotFoundApp,
ChanID: invalid,
},
{
desc: "remove channel with empty channelID",
err: lora.ErrNotFoundApp,
ChanID: "",
},
}
for _, tc := range cases {
repoCall := channelsRM.On("Remove", context.Background(), tc.ChanID).Return(tc.err)
err := svc.RemoveChannel(context.Background(), tc.ChanID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
func TestRemoveThing(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ThingID string
}{
{
desc: "remove thing with valid data",
err: nil,
ThingID: thingID,
},
{
desc: "remove thing with non existing thing",
err: lora.ErrNotFoundDev,
ThingID: invalid,
},
{
desc: "remove thing with empty thingID",
err: lora.ErrNotFoundDev,
ThingID: "",
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Remove", context.Background(), tc.ThingID).Return(tc.err)
err := svc.RemoveThing(context.Background(), tc.ThingID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
func TestUpdateChannel(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ChanID string
AppID string
}{
{
desc: "update channel with valid data",
err: nil,
ChanID: chanID,
AppID: appID,
},
{
desc: "update channel with non existing channel",
err: lora.ErrNotFoundApp,
ChanID: invalid,
AppID: appID,
},
{
desc: "update channel with empty channelID",
err: lora.ErrNotFoundApp,
ChanID: "",
AppID: appID,
},
{
desc: "update channel with empty appID",
err: lora.ErrNotFoundApp,
ChanID: chanID,
AppID: "",
},
{
desc: "update channel with non existing appID",
err: lora.ErrNotFoundApp,
ChanID: chanID,
AppID: invalid,
},
}
for _, tc := range cases {
repoCall := channelsRM.On("Save", context.Background(), tc.ChanID, tc.AppID).Return(tc.err)
err := svc.UpdateChannel(context.Background(), tc.ChanID, tc.AppID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
func TestUpdateThing(t *testing.T) {
svc := newService()
cases := []struct {
desc string
err error
ThingID string
DevEUI string
}{
{
desc: "update thing with valid data",
err: nil,
ThingID: thingID,
DevEUI: devEUI,
},
{
desc: "update thing with non existing thing",
err: lora.ErrNotFoundDev,
ThingID: invalid,
DevEUI: devEUI,
},
{
desc: "update thing with empty thingID",
err: lora.ErrNotFoundDev,
ThingID: "",
DevEUI: devEUI,
},
{
desc: "update thing with empty devEUI",
err: lora.ErrNotFoundDev,
ThingID: thingID,
DevEUI: "",
},
{
desc: "update thing with non existing devEUI",
err: lora.ErrNotFoundDev,
ThingID: thingID,
DevEUI: invalid,
},
}
for _, tc := range cases {
repoCall := thingsRM.On("Save", context.Background(), tc.ThingID, tc.DevEUI).Return(tc.err)
err := svc.UpdateThing(context.Background(), tc.ThingID, tc.DevEUI)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
}
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"net/http"
"github.com/absmach/magistrala"
"github.com/go-chi/chi/v5"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(instanceID string) http.Handler {
r := chi.NewRouter()
r.Get("/health", magistrala.Health("lora-adapter", instanceID))
r.Handle("/metrics", promhttp.Handler())
return r
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package api contains API-related concerns: endpoint definitions, middlewares
// and all resource representations.
package api
-189
View File
@@ -1,189 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package api
import (
"context"
"log/slog"
"time"
"github.com/absmach/magistrala/lora"
)
var _ lora.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger *slog.Logger
svc lora.Service
}
// LoggingMiddleware adds logging facilities to the core service.
func LoggingMiddleware(svc lora.Service, logger *slog.Logger) lora.Service {
return &loggingMiddleware{
logger: logger,
svc: svc,
}
}
func (lm loggingMiddleware) CreateThing(ctx context.Context, thingID, loraDevEUI string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", thingID),
slog.String("dev_eui", loraDevEUI),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Create thing route-map failed", args...)
return
}
lm.logger.Info("Create thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.CreateThing(ctx, thingID, loraDevEUI)
}
func (lm loggingMiddleware) UpdateThing(ctx context.Context, thingID, loraDevEUI string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", thingID),
slog.String("dev_eui", loraDevEUI),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update thing route-map failed", args...)
return
}
lm.logger.Info("Update thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.UpdateThing(ctx, thingID, loraDevEUI)
}
func (lm loggingMiddleware) RemoveThing(ctx context.Context, thingID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", thingID),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Remove thing route-map failed", args...)
return
}
lm.logger.Info("Remove thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.RemoveThing(ctx, thingID)
}
func (lm loggingMiddleware) CreateChannel(ctx context.Context, chanID, loraApp string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", chanID),
slog.String("lora_app", loraApp),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Create channel route-map failed", args...)
return
}
lm.logger.Info("Create channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.CreateChannel(ctx, chanID, loraApp)
}
func (lm loggingMiddleware) UpdateChannel(ctx context.Context, chanID, loraApp string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", chanID),
slog.String("lora_app", loraApp),
}
if err != nil {
lm.logger.Warn("Update channel route-map failed", args...)
return
}
lm.logger.Info("Update channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.UpdateChannel(ctx, chanID, loraApp)
}
func (lm loggingMiddleware) RemoveChannel(ctx context.Context, chanID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", chanID),
}
if err != nil {
lm.logger.Warn("Remove channel route-map failed", args...)
return
}
lm.logger.Info("Remove channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.RemoveChannel(ctx, chanID)
}
func (lm loggingMiddleware) ConnectThing(ctx context.Context, chanID, thingID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", chanID),
slog.String("thing_id", thingID),
}
if err != nil {
args := append(args, slog.String("error", err.Error()))
lm.logger.Warn("Connect thing to channel failed", args...)
return
}
lm.logger.Info("Connect thing to channel completed successfully", args...)
}(time.Now())
return lm.svc.ConnectThing(ctx, chanID, thingID)
}
func (lm loggingMiddleware) DisconnectThing(ctx context.Context, chanID, thingID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", chanID),
slog.String("thing_id", thingID),
}
if err != nil {
args := append(args, slog.String("error", err.Error()))
lm.logger.Warn("Disconnect thing from channel failed", args...)
return
}
lm.logger.Info("Disconnect thing from channel completed successfully", args...)
}(time.Now())
return lm.svc.DisconnectThing(ctx, chanID, thingID)
}
func (lm loggingMiddleware) Publish(ctx context.Context, msg *lora.Message) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.Group("message",
slog.String("application_id", msg.ApplicationID),
slog.String("device_eui", msg.DevEUI),
),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Publish failed", args...)
return
}
lm.logger.Info("Publish completed successfully", args...)
}(time.Now())
return lm.svc.Publish(ctx, msg)
}
-112
View File
@@ -1,112 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package api
import (
"context"
"time"
"github.com/absmach/magistrala/lora"
"github.com/go-kit/kit/metrics"
)
var _ lora.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc lora.Service
}
// MetricsMiddleware instruments core service by tracking request count and latency.
func MetricsMiddleware(svc lora.Service, counter metrics.Counter, latency metrics.Histogram) lora.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (mm *metricsMiddleware) CreateThing(ctx context.Context, thingID, loraDevEUI string) error {
defer func(begin time.Time) {
mm.counter.With("method", "create_thing").Add(1)
mm.latency.With("method", "create_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.CreateThing(ctx, thingID, loraDevEUI)
}
func (mm *metricsMiddleware) UpdateThing(ctx context.Context, thingID, loraDevEUI string) error {
defer func(begin time.Time) {
mm.counter.With("method", "update_thing").Add(1)
mm.latency.With("method", "update_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateThing(ctx, thingID, loraDevEUI)
}
func (mm *metricsMiddleware) RemoveThing(ctx context.Context, thingID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "remove_thing").Add(1)
mm.latency.With("method", "remove_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveThing(ctx, thingID)
}
func (mm *metricsMiddleware) CreateChannel(ctx context.Context, chanID, loraApp string) error {
defer func(begin time.Time) {
mm.counter.With("method", "create_channel").Add(1)
mm.latency.With("method", "create_channel").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.CreateChannel(ctx, chanID, loraApp)
}
func (mm *metricsMiddleware) UpdateChannel(ctx context.Context, chanID, loraApp string) 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.UpdateChannel(ctx, chanID, loraApp)
}
func (mm *metricsMiddleware) RemoveChannel(ctx context.Context, chanID string) 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.RemoveChannel(ctx, chanID)
}
func (mm *metricsMiddleware) ConnectThing(ctx context.Context, chanID, thingID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "connect_thing").Add(1)
mm.latency.With("method", "connect_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.ConnectThing(ctx, chanID, thingID)
}
func (mm *metricsMiddleware) DisconnectThing(ctx context.Context, chanID, thingID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "disconnect_thing").Add(1)
mm.latency.With("method", "disconnect_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.DisconnectThing(ctx, chanID, thingID)
}
func (mm *metricsMiddleware) Publish(ctx context.Context, msg *lora.Message) 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, msg)
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package lora contains the domain concept definitions needed to support
// Magistrala LoRa service functionality.
package lora
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package events provides the domain concept definitions needed to support
// lora events functionality.
package events
-27
View File
@@ -1,27 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package events
type createThingEvent struct {
id string
loraDevEUI string
}
type removeThingEvent struct {
id string
}
type createChannelEvent struct {
id string
loraAppID string
}
type removeChannelEvent struct {
id string
}
type connectionThingEvent struct {
chanID string
thingIDs []string
}
-58
View File
@@ -1,58 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package events
import (
"context"
"fmt"
"github.com/absmach/magistrala/lora"
"github.com/go-redis/redis/v8"
)
var _ lora.RouteMapRepository = (*routerMap)(nil)
type routerMap struct {
client *redis.Client
prefix string
}
// NewRouteMapRepository returns redis thing cache implementation.
func NewRouteMapRepository(client *redis.Client, prefix string) lora.RouteMapRepository {
return &routerMap{
client: client,
prefix: prefix,
}
}
func (mr *routerMap) Save(ctx context.Context, mgxID, loraID string) error {
tkey := fmt.Sprintf("%s:%s", mr.prefix, mgxID)
if err := mr.client.Set(ctx, tkey, loraID, 0).Err(); err != nil {
return err
}
lkey := fmt.Sprintf("%s:%s", mr.prefix, loraID)
return mr.client.Set(ctx, lkey, mgxID, 0).Err()
}
func (mr *routerMap) Get(ctx context.Context, id string) (string, error) {
lKey := fmt.Sprintf("%s:%s", mr.prefix, id)
mval, err := mr.client.Get(ctx, lKey).Result()
if err != nil {
return "", err
}
return mval, nil
}
func (mr *routerMap) Remove(ctx context.Context, mgxID string) error {
mkey := fmt.Sprintf("%s:%s", mr.prefix, mgxID)
lval, err := mr.client.Get(ctx, mkey).Result()
if err != nil {
return err
}
lkey := fmt.Sprintf("%s:%s", mr.prefix, lval)
return mr.client.Del(ctx, mkey, lkey).Err()
}
-175
View File
@@ -1,175 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package events
import (
"context"
"errors"
"github.com/absmach/magistrala/lora"
"github.com/absmach/magistrala/pkg/events"
)
const (
keyType = "lora"
keyDevEUI = "dev_eui"
keyAppID = "app_id"
thingPrefix = "thing."
thingCreate = thingPrefix + "create"
thingUpdate = thingPrefix + "update"
thingRemove = thingPrefix + "remove"
thingConnect = thingPrefix + "connect"
thingDisconnect = thingPrefix + "disconnect"
channelPrefix = "group."
channelCreate = channelPrefix + "create"
channelUpdate = channelPrefix + "update"
channelRemove = channelPrefix + "remove"
)
var (
errMetadataType = errors.New("field lora is missing in the metadata")
errMetadataFormat = errors.New("malformed metadata")
errMetadataAppID = errors.New("application ID not found in channel metadatada")
errMetadataDevEUI = errors.New("device EUI not found in thing metadatada")
)
type eventHandler struct {
svc lora.Service
}
// NewEventHandler returns new event store handler.
func NewEventHandler(svc lora.Service) events.EventHandler {
return &eventHandler{
svc: svc,
}
}
func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
msg, err := event.Encode()
if err != nil {
return err
}
switch msg["operation"] {
case thingCreate, thingUpdate:
cte, derr := decodeCreateThing(msg)
if derr != nil {
err = derr
break
}
err = es.svc.CreateThing(ctx, cte.id, cte.loraDevEUI)
case channelCreate, channelUpdate:
cce, derr := decodeCreateChannel(msg)
if derr != nil {
err = derr
break
}
err = es.svc.CreateChannel(ctx, cce.id, cce.loraAppID)
case thingRemove:
rte := decodeRemoveThing(msg)
err = es.svc.RemoveThing(ctx, rte.id)
case channelRemove:
rce := decodeRemoveChannel(msg)
err = es.svc.RemoveChannel(ctx, rce.id)
case thingConnect:
tce := decodeConnectionThing(msg)
for _, thingID := range tce.thingIDs {
err = es.svc.ConnectThing(ctx, tce.chanID, thingID)
if err != nil {
return err
}
}
case thingDisconnect:
tde := decodeConnectionThing(msg)
for _, thingID := range tde.thingIDs {
err = es.svc.DisconnectThing(ctx, tde.chanID, thingID)
if err != nil {
return err
}
}
}
if err != nil && err != errMetadataType {
return err
}
return nil
}
func decodeCreateThing(event map[string]interface{}) (createThingEvent, error) {
metadata := events.Read(event, "metadata", map[string]interface{}{})
cte := createThingEvent{
id: events.Read(event, "id", ""),
}
m, ok := metadata[keyType]
if !ok {
return createThingEvent{}, errMetadataType
}
lm, ok := m.(map[string]interface{})
if !ok {
return createThingEvent{}, errMetadataFormat
}
val, ok := lm[keyDevEUI].(string)
if !ok {
return createThingEvent{}, errMetadataDevEUI
}
cte.loraDevEUI = val
return cte, nil
}
func decodeRemoveThing(event map[string]interface{}) removeThingEvent {
return removeThingEvent{
id: events.Read(event, "id", ""),
}
}
func decodeCreateChannel(event map[string]interface{}) (createChannelEvent, error) {
metadata := events.Read(event, "metadata", map[string]interface{}{})
cce := createChannelEvent{
id: events.Read(event, "id", ""),
}
m, ok := metadata[keyType]
if !ok {
return createChannelEvent{}, errMetadataType
}
lm, ok := m.(map[string]interface{})
if !ok {
return createChannelEvent{}, errMetadataFormat
}
val, ok := lm[keyAppID].(string)
if !ok {
return createChannelEvent{}, errMetadataAppID
}
cce.loraAppID = val
return cce, nil
}
func decodeConnectionThing(event map[string]interface{}) connectionThingEvent {
return connectionThingEvent{
chanID: events.Read(event, "group_id", ""),
thingIDs: events.ReadStringSlice(event, "member_ids"),
}
}
func decodeRemoveChannel(event map[string]interface{}) removeChannelEvent {
return removeChannelEvent{
id: events.Read(event, "id", ""),
}
}
-47
View File
@@ -1,47 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package lora
// RxInfo receiver parameters.
type RxInfo []struct {
Mac string `json:"mac"`
Name string `json:"name"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude"`
Time string `json:"time"`
Rssi float64 `json:"rssi"`
LoRaSNR float64 `json:"loRaSNR"`
}
// DataRate lora data rate.
type DataRate struct {
Modulation string `json:"modulation"`
Bandwidth float64 `json:"bandwidth"`
SpreadFactor int64 `json:"spreadFactor"`
}
// TxInfo transmeter parameters.
type TxInfo struct {
Frequency float64 `json:"frequency"`
DataRate DataRate `json:"dataRate"`
Adr bool `json:"adr"`
CodeRate string `json:"codeRate"`
}
// Message lora msg (https://www.chirpstack.io/application-server/integrations/events).
type Message struct {
ApplicationID string `json:"applicationID"`
ApplicationName string `json:"applicationName"`
DeviceName string `json:"deviceName"`
DevEUI string `json:"devEUI"`
DeviceStatusBattery string `json:"deviceStatusBattery"`
DeviceStatusMrgin string `json:"deviceStatusMargin"`
RxInfo RxInfo `json:"rxInfo"`
TxInfo TxInfo `json:"txInfo"`
FCnt int `json:"fCnt"`
FPort int `json:"fPort"`
Data string `json:"data"`
Object interface{} `json:"object"`
}
-5
View File
@@ -1,5 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package mocks contains mocks for testing purposes.
package mocks
-94
View File
@@ -1,94 +0,0 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Abstract Machines
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// RouteMapRepository is an autogenerated mock type for the RouteMapRepository type
type RouteMapRepository struct {
mock.Mock
}
// Get provides a mock function with given fields: _a0, _a1
func (_m *RouteMapRepository) Get(_a0 context.Context, _a1 string) (string, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, string) string); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Remove provides a mock function with given fields: _a0, _a1
func (_m *RouteMapRepository) Remove(_a0 context.Context, _a1 string) error {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Remove")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: _a0, _a1, _a2
func (_m *RouteMapRepository) Save(_a0 context.Context, _a1 string, _a2 string) error {
ret := _m.Called(_a0, _a1, _a2)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(_a0, _a1, _a2)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewRouteMapRepository creates a new instance of RouteMapRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRouteMapRepository(t interface {
mock.TestingT
Cleanup(func())
}) *RouteMapRepository {
mock := &RouteMapRepository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package mqtt contains the domain concept definitions needed to
// support Magistrala MQTT adapter functionality.
package mqtt
-62
View File
@@ -1,62 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mqtt
// LoraSubscribe subscribe to lora server messages.
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"time"
"github.com/absmach/magistrala/lora"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
// Subscriber represents the MQTT broker.
type Subscriber interface {
// Subscribes to given subject and receives events.
Subscribe(string) error
}
type broker struct {
svc lora.Service
client mqtt.Client
logger *slog.Logger
timeout time.Duration
}
// NewBroker returns new MQTT broker instance.
func NewBroker(svc lora.Service, client mqtt.Client, t time.Duration, log *slog.Logger) Subscriber {
return broker{
svc: svc,
client: client,
logger: log,
timeout: t,
}
}
// Subscribe subscribes to the Lora MQTT message broker.
func (b broker) Subscribe(subject string) error {
s := b.client.Subscribe(subject, 0, b.handleMsg)
if err := s.Error(); s.WaitTimeout(b.timeout) && err != nil {
return err
}
return nil
}
// handleMsg triggered when new message is received on Lora MQTT broker.
func (b broker) handleMsg(c mqtt.Client, msg mqtt.Message) {
m := lora.Message{}
if err := json.Unmarshal(msg.Payload(), &m); err != nil {
b.logger.Warn(fmt.Sprintf("Failed to unmarshal message: %s", err.Error()))
return
}
if err := b.svc.Publish(context.Background(), &m); err != nil {
b.logger.Error(fmt.Sprintf("got error while publishing messages: %s", err))
}
}
-20
View File
@@ -1,20 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package lora
import "context"
// RouteMapRepository store route map between Lora App Server and Magistrala.
//
//go:generate mockery --name RouteMapRepository --output=./mocks --filename routes.go --quiet --note "Copyright (c) Abstract Machines"
type RouteMapRepository interface {
// Save stores/routes pair lora application topic & magistrala channel.
Save(context.Context, string, string) error
// Channel returns magistrala channel for given lora application.
Get(context.Context, string) (string, error)
// Removes mapping from cache.
Remove(context.Context, string) error
}
-77
View File
@@ -1,77 +0,0 @@
# OPC-UA Adapter
Adapter between Magistrala IoT system and an OPC-UA Server.
This adapter sits between Magistrala and an OPC-UA server and just forwards the messages from one system to another.
OPC-UA Server is used for connectivity layer and the data is pushed via this adapter service to Magistrala, where it is persisted and routed to other protocols via Magistrala multi-protocol message broker. Magistrala adds user accounts, application management and security in order to obtain the overall end-to-end OPC-UA solution.
## 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 |
| --------------------------------- | ------------------------------------------------------- | ----------------------------------- |
| MG_OPCUA_ADAPTER_LOG_LEVEL | Log level for the WS Adapter (debug, info, warn, error) | info |
| MG_OPCUA_ADAPTER_HTTP_HOST | Service OPC-UA host | "" |
| MG_OPCUA_ADAPTER_HTTP_PORT | Service WOPC-UAS port | 8180 |
| MG_OPCUA_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" |
| MG_OPCUA_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" |
| MG_OPCUA_ADAPTER_ROUTE_MAP_URL | Route-map database URL | <redis://localhost:6379/0> |
| MG_ES_URL | Event source URL | <nats://localhost:4222> |
| MG_OPCUA_ADAPTER_EVENT_CONSUMER | Service event consumer name | opcua-adapter |
| MG_MESSAGE_BROKER_URL | Message broker instance URL | <nats://localhost:4222> |
| MG_JAEGER_URL | Jaeger server URL | <http://localhost:14268/api/traces> |
| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_OPCUA_ADAPTER_INSTANCE_ID | Service instance ID | "" |
## Deployment
The service itself is distributed as Docker container. Check the [`opcua-adapter`](https://github.com/absmach/magistrala/blob/main/docker/addons/opcua-adapter/docker-compose.yml) service section in docker-compose file to see how service is deployed.
Running this service outside of container requires working instance of the message broker service, redis routemap server and Jaeger server.
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
git clone https://github.com/absmach/magistrala
cd magistrala
# compile the opcua-adapter
make opcua
# copy binary to bin
make install
# set the environment variables and run the service
MG_OPCUA_ADAPTER_LOG_LEVEL=info \
MG_OPCUA_ADAPTER_HTTP_HOST=localhost \
MG_OPCUA_ADAPTER_HTTP_PORT=8180 \
MG_OPCUA_ADAPTER_HTTP_SERVER_CERT="" \
MG_OPCUA_ADAPTER_HTTP_SERVER_KEY="" \
MG_OPCUA_ADAPTER_ROUTE_MAP_URL=redis://localhost:6379/0 \
MG_ES_URL=nats://localhost:4222 \
MG_OPCUA_ADAPTER_EVENT_CONSUMER=opcua-adapter \
MG_MESSAGE_BROKER_URL=nats://localhost:4222 \
MG_JAEGER_URL=http://localhost:14268/api/traces \
MG_JAEGER_TRACE_RATIO=1.0 \
MG_SEND_TELEMETRY=true \
MG_OPCUA_ADAPTER_INSTANCE_ID="" \
$GOBIN/magistrala-opcua
```
Setting `MG_LORA_ADAPTER_HTTP_SERVER_CERT` and `MG_LORA_ADAPTER_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
### Using docker-compose
This service can be deployed using docker containers. Docker compose file is available in `<project_root>/docker/addons/opcua-adapter/docker-compose.yml`. In order to run Magistrala opcua-adapter, execute the following command:
```bash
docker compose -f docker/addons/opcua-adapter/docker-compose.yml up -d
```
## Usage
For more information about service capabilities and its usage, please check out the [Magistrala documentation](https://docs.magistrala.abstractmachines.fr/opcua).
-200
View File
@@ -1,200 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package opcua
import (
"context"
"encoding/base64"
"fmt"
"log/slog"
"regexp"
"strconv"
"github.com/absmach/magistrala/opcua/db"
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// CreateThing creates thingID:OPC-UA-nodeID route-map
CreateThing(ctx context.Context, thingID, nodeID string) error
// UpdateThing updates thingID:OPC-UA-nodeID route-map
UpdateThing(ctx context.Context, thingID, nodeID string) error
// RemoveThing removes thingID:OPC-UA-nodeID route-map
RemoveThing(ctx context.Context, thingID string) error
// CreateChannel creates channelID:OPC-UA-serverURI route-map
CreateChannel(ctx context.Context, chanID, serverURI string) error
// UpdateChannel updates channelID:OPC-UA-serverURI route-map
UpdateChannel(ctx context.Context, chanID, serverURI string) error
// RemoveChannel removes channelID:OPC-UA-serverURI route-map
RemoveChannel(ctx context.Context, chanID string) error
// ConnectThing creates thingID:channelID route-map
ConnectThing(ctx context.Context, chanID string, thingIDs []string) error
// DisconnectThing removes thingID:channelID route-map
DisconnectThing(ctx context.Context, chanID string, thingIDs []string) error
// Browse browses available nodes for a given OPC-UA Server URI and NodeID
Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]BrowsedNode, error)
}
// Config OPC-UA Server.
type Config struct {
ServerURI string
NodeID string
Interval string `env:"MG_OPCUA_ADAPTER_INTERVAL_MS" envDefault:"1000"`
Policy string `env:"MG_OPCUA_ADAPTER_POLICY" envDefault:""`
Mode string `env:"MG_OPCUA_ADAPTER_MODE" envDefault:""`
CertFile string `env:"MG_OPCUA_ADAPTER_CERT_FILE" envDefault:""`
KeyFile string `env:"MG_OPCUA_ADAPTER_KEY_FILE" envDefault:""`
}
var (
_ Service = (*adapterService)(nil)
guidRegex = regexp.MustCompile(`^\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\}?$`)
)
type adapterService struct {
subscriber Subscriber
browser Browser
thingsRM RouteMapRepository
channelsRM RouteMapRepository
connectRM RouteMapRepository
cfg Config
logger *slog.Logger
}
// New instantiates the OPC-UA adapter implementation.
func New(sub Subscriber, brow Browser, thingsRM, channelsRM, connectRM RouteMapRepository, cfg Config, log *slog.Logger) Service {
return &adapterService{
subscriber: sub,
browser: brow,
thingsRM: thingsRM,
channelsRM: channelsRM,
connectRM: connectRM,
cfg: cfg,
logger: log,
}
}
func (as *adapterService) CreateThing(ctx context.Context, thingID, nodeID string) error {
return as.thingsRM.Save(ctx, thingID, nodeID)
}
func (as *adapterService) UpdateThing(ctx context.Context, thingID, nodeID string) error {
return as.thingsRM.Save(ctx, thingID, nodeID)
}
func (as *adapterService) RemoveThing(ctx context.Context, thingID string) error {
return as.thingsRM.Remove(ctx, thingID)
}
func (as *adapterService) CreateChannel(ctx context.Context, chanID, serverURI string) error {
return as.channelsRM.Save(ctx, chanID, serverURI)
}
func (as *adapterService) UpdateChannel(ctx context.Context, chanID, serverURI string) error {
return as.channelsRM.Save(ctx, chanID, serverURI)
}
func (as *adapterService) RemoveChannel(ctx context.Context, chanID string) error {
return as.channelsRM.Remove(ctx, chanID)
}
func (as *adapterService) ConnectThing(ctx context.Context, chanID string, thingIDs []string) error {
serverURI, err := as.channelsRM.Get(ctx, chanID)
if err != nil {
return err
}
for _, thingID := range thingIDs {
nodeID, err := as.thingsRM.Get(ctx, thingID)
if err != nil {
return err
}
as.cfg.NodeID = nodeID
as.cfg.ServerURI = serverURI
c := fmt.Sprintf("%s:%s", chanID, thingID)
if err := as.connectRM.Save(ctx, c, c); err != nil {
return err
}
go func() {
if err := as.subscriber.Subscribe(ctx, as.cfg); err != nil {
as.logger.Warn("subscription failed", slog.Any("error", err))
}
}()
// Store subscription details
if err := db.Save(serverURI, nodeID); err != nil {
return err
}
}
return nil
}
func (as *adapterService) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]BrowsedNode, error) {
idFormat := "s"
switch identifierType {
case "string":
break
case "numeric":
if _, err := strconv.Atoi(identifier); err != nil {
args := []any{
slog.String("namespace", namespace),
slog.String("identifier", identifier),
slog.Any("error", err),
}
as.logger.Warn("failed to parse numeric identifier", args...)
break
}
idFormat = "i"
case "guid":
if !guidRegex.MatchString(identifier) {
args := []any{
slog.String("namespace", namespace),
slog.String("identifier", identifier),
}
as.logger.Warn("GUID identifier has invalid format", args...)
break
}
idFormat = "g"
case "opaque":
if _, err := base64.StdEncoding.DecodeString(identifier); err != nil {
args := []any{
slog.String("namespace", namespace),
slog.String("identifier", identifier),
slog.Any("error", err),
}
as.logger.Warn("opaque identifier has invalid base64 format", args...)
break
}
idFormat = "b"
}
nodeID := fmt.Sprintf("ns=%s;%s=%s", namespace, idFormat, identifier)
nodes, err := as.browser.Browse(serverURI, nodeID)
if err != nil {
return nil, err
}
return nodes, nil
}
func (as *adapterService) DisconnectThing(ctx context.Context, chanID string, thingIDs []string) error {
for _, thingID := range thingIDs {
c := fmt.Sprintf("%s:%s", chanID, thingID)
if err := as.connectRM.Remove(ctx, c); err != nil {
return err
}
}
return nil
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package api contains API-related concerns: endpoint definitions, middlewares
// and all resource representations.
package api
-34
View File
@@ -1,34 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"github.com/absmach/magistrala/opcua"
"github.com/absmach/magistrala/pkg/apiutil"
"github.com/absmach/magistrala/pkg/errors"
"github.com/go-kit/kit/endpoint"
)
func browseEndpoint(svc opcua.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(browseReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
nodes, err := svc.Browse(ctx, req.ServerURI, req.Namespace, req.Identifier, req.IdentifierType)
if err != nil {
return nil, err
}
res := browseRes{
Nodes: nodes,
}
return res, nil
}
}
-191
View File
@@ -1,191 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package api
import (
"context"
"log/slog"
"time"
"github.com/absmach/magistrala/opcua"
)
var _ opcua.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger *slog.Logger
svc opcua.Service
}
// LoggingMiddleware adds logging facilities to the core service.
func LoggingMiddleware(svc opcua.Service, logger *slog.Logger) opcua.Service {
return &loggingMiddleware{
logger: logger,
svc: svc,
}
}
func (lm loggingMiddleware) CreateThing(ctx context.Context, mgxThing, opcuaNodeID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", mgxThing),
slog.String("node_id", opcuaNodeID),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Create thing route-map failed", args...)
return
}
lm.logger.Info("Create thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.CreateThing(ctx, mgxThing, opcuaNodeID)
}
func (lm loggingMiddleware) UpdateThing(ctx context.Context, mgxThing, opcuaNodeID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", mgxThing),
slog.String("node_id", opcuaNodeID),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update thing route-map failed", args...)
return
}
lm.logger.Info("Update thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.UpdateThing(ctx, mgxThing, opcuaNodeID)
}
func (lm loggingMiddleware) RemoveThing(ctx context.Context, mgxThing string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("thing_id", mgxThing),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Remove thing route-map failed", args...)
return
}
lm.logger.Info("Remove thing route-map completed successfully", args...)
}(time.Now())
return lm.svc.RemoveThing(ctx, mgxThing)
}
func (lm loggingMiddleware) CreateChannel(ctx context.Context, mgxChan, opcuaServerURI string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", mgxChan),
slog.String("server_uri", opcuaServerURI),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Create channel route-map failed", args...)
return
}
lm.logger.Info("Create channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.CreateChannel(ctx, mgxChan, opcuaServerURI)
}
func (lm loggingMiddleware) UpdateChannel(ctx context.Context, mgxChanID, opcuaServerURI string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", mgxChanID),
slog.String("server_uri", opcuaServerURI),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Update channel route-map failed", args...)
return
}
lm.logger.Info("Update channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.UpdateChannel(ctx, mgxChanID, opcuaServerURI)
}
func (lm loggingMiddleware) RemoveChannel(ctx context.Context, mgxChanID string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", mgxChanID),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Remove channel route-map failed", args...)
return
}
lm.logger.Info("Remove channel route-map completed successfully", args...)
}(time.Now())
return lm.svc.RemoveChannel(ctx, mgxChanID)
}
func (lm loggingMiddleware) ConnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", mgxChanID),
slog.Any("thing_ids", mgxThingIDs),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Connect thing to channel failed", args...)
return
}
lm.logger.Info("Connect thing to channel completed successfully", args...)
}(time.Now())
return lm.svc.ConnectThing(ctx, mgxChanID, mgxThingIDs)
}
func (lm loggingMiddleware) DisconnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) (err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("channel_id", mgxChanID),
slog.Any("thing_ids", mgxThingIDs),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Disconnect thing from channel failed", args...)
return
}
lm.logger.Info("Disconnect thing from channel completed successfully", args...)
}(time.Now())
return lm.svc.DisconnectThing(ctx, mgxChanID, mgxThingIDs)
}
func (lm loggingMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) (nodes []opcua.BrowsedNode, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("server_uri", serverURI),
slog.String("namespace", namespace),
slog.String("identifier", identifier),
slog.String("identifier_type", identifierType),
}
if err != nil {
args = append(args, slog.Any("error", err))
lm.logger.Warn("Browse available nodes failed", args...)
return
}
lm.logger.Info("Browse available nodes completed successfully", args...)
}(time.Now())
return lm.svc.Browse(ctx, serverURI, namespace, identifier, identifierType)
}
-112
View File
@@ -1,112 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
//go:build !test
package api
import (
"context"
"time"
"github.com/absmach/magistrala/opcua"
"github.com/go-kit/kit/metrics"
)
var _ opcua.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc opcua.Service
}
// MetricsMiddleware instruments core service by tracking request count and latency.
func MetricsMiddleware(svc opcua.Service, counter metrics.Counter, latency metrics.Histogram) opcua.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (mm *metricsMiddleware) CreateThing(ctx context.Context, mgxDevID, opcuaNodeID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "create_thing").Add(1)
mm.latency.With("method", "create_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.CreateThing(ctx, mgxDevID, opcuaNodeID)
}
func (mm *metricsMiddleware) UpdateThing(ctx context.Context, mgxDevID, opcuaNodeID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "update_thing").Add(1)
mm.latency.With("method", "update_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateThing(ctx, mgxDevID, opcuaNodeID)
}
func (mm *metricsMiddleware) RemoveThing(ctx context.Context, mgxDevID string) error {
defer func(begin time.Time) {
mm.counter.With("method", "remove_thing").Add(1)
mm.latency.With("method", "remove_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveThing(ctx, mgxDevID)
}
func (mm *metricsMiddleware) CreateChannel(ctx context.Context, mgxChanID, opcuaServerURI string) error {
defer func(begin time.Time) {
mm.counter.With("method", "create_channel").Add(1)
mm.latency.With("method", "create_channel").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.CreateChannel(ctx, mgxChanID, opcuaServerURI)
}
func (mm *metricsMiddleware) UpdateChannel(ctx context.Context, mgxChanID, opcuaServerURI string) 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.UpdateChannel(ctx, mgxChanID, opcuaServerURI)
}
func (mm *metricsMiddleware) RemoveChannel(ctx context.Context, mgxChanID string) 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.RemoveChannel(ctx, mgxChanID)
}
func (mm *metricsMiddleware) ConnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) error {
defer func(begin time.Time) {
mm.counter.With("method", "connect_thing").Add(1)
mm.latency.With("method", "connect_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.ConnectThing(ctx, mgxChanID, mgxThingIDs)
}
func (mm *metricsMiddleware) DisconnectThing(ctx context.Context, mgxChanID string, mgxThingIDs []string) error {
defer func(begin time.Time) {
mm.counter.With("method", "disconnect_thing").Add(1)
mm.latency.With("method", "disconnect_thing").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.DisconnectThing(ctx, mgxChanID, mgxThingIDs)
}
func (mm *metricsMiddleware) Browse(ctx context.Context, serverURI, namespace, identifier, identifierType string) ([]opcua.BrowsedNode, error) {
defer func(begin time.Time) {
mm.counter.With("method", "browse").Add(1)
mm.latency.With("method", "browse").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Browse(ctx, serverURI, namespace, identifier, identifierType)
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import "github.com/absmach/magistrala/pkg/apiutil"
type browseReq struct {
ServerURI string
Namespace string
Identifier string
IdentifierType string
}
func (req *browseReq) validate() error {
if req.ServerURI == "" {
return apiutil.ErrMissingID
}
return nil
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"net/http"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/opcua"
)
var _ magistrala.Response = (*browseRes)(nil)
type browseRes struct {
Nodes []opcua.BrowsedNode `json:"nodes"`
}
func (res browseRes) Code() int {
return http.StatusOK
}
func (res browseRes) Headers() map[string]string {
return map[string]string{}
}
func (res browseRes) Empty() bool {
return false
}
-132
View File
@@ -1,132 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"github.com/absmach/magistrala"
"github.com/absmach/magistrala/opcua"
"github.com/absmach/magistrala/pkg/apiutil"
"github.com/absmach/magistrala/pkg/errors"
"github.com/go-chi/chi/v5"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
contentType = "application/json"
serverParam = "server"
namespaceParam = "namespace"
identifierParam = "identifier"
identifierTypeParam = "identifierType"
defNamespace = "ns=0" // Standard root namespace
defIdentifier = "i=84" // Standard root identifier
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc opcua.Service, logger *slog.Logger, instanceID string) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)),
}
r := chi.NewRouter()
r.Get("/browse", kithttp.NewServer(
browseEndpoint(svc),
decodeBrowse,
encodeResponse,
opts...,
).ServeHTTP)
r.Get("/health", magistrala.Health("opcua-adapter", instanceID))
r.Handle("/metrics", promhttp.Handler())
return r
}
func decodeBrowse(_ context.Context, r *http.Request) (interface{}, error) {
s, err := apiutil.ReadStringQuery(r, serverParam, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
n, err := apiutil.ReadStringQuery(r, namespaceParam, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
i, err := apiutil.ReadStringQuery(r, identifierParam, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
iType, err := apiutil.ReadStringQuery(r, identifierTypeParam, "")
if err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
if n == "" || i == "" {
n = defNamespace
i = defIdentifier
}
req := browseReq{
ServerURI: s,
Namespace: n,
Identifier: i,
IdentifierType: iType,
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
if ar, ok := response.(magistrala.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) {
var wrapper error
if errors.Contains(err, apiutil.ErrValidation) {
wrapper, err = errors.Unwrap(err)
}
switch {
case errors.Contains(err, apiutil.ErrInvalidQueryParams),
errors.Contains(err, errors.ErrMalformedEntity),
err == apiutil.ErrMissingID:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
if wrapper != nil {
err = errors.Wrap(wrapper, err)
}
if errorVal, ok := err.(errors.Error); ok {
w.Header().Set("Content-Type", contentType)
if err := json.NewEncoder(w).Encode(errorVal); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
}
-20
View File
@@ -1,20 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package opcua
// BrowsedNode represents the details of a browsed OPC-UA node.
type BrowsedNode struct {
NodeID string
DataType string
Description string
Unit string
Scale string
BrowseName string
}
// Browser represents the OPC-UA Server Nodes browser.
type Browser interface {
// Browse availlable Nodes for a given URI.
Browse(string, string) ([]BrowsedNode, error)
}
-5
View File
@@ -1,5 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package db contains the database implementation of opcua repository layer.
package db
-81
View File
@@ -1,81 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package db
import (
"encoding/csv"
"io"
"os"
"github.com/absmach/magistrala/pkg/errors"
)
const (
columns = 2
path = "/store/nodes.csv"
)
var (
errNotFound = errors.New("file not found")
errWriteFile = errors.New("failed de write file")
errOpenFile = errors.New("failed to open file")
errReadFile = errors.New("failed to read file")
errEmptyLine = errors.New("empty or incomplete line found in file")
)
// Node represents an OPC-UA node.
type Node struct {
ServerURI string
NodeID string
}
// Save stores a successful subscription.
func Save(serverURI, nodeID string) error {
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
if err != nil {
return errors.Wrap(errWriteFile, err)
}
defer file.Close()
csvWriter := csv.NewWriter(file)
err = csvWriter.Write([]string{serverURI, nodeID})
csvWriter.Flush()
if err != nil {
return errors.Wrap(errWriteFile, err)
}
return nil
}
// ReadAll returns all stored subscriptions.
func ReadAll() ([]Node, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, errors.Wrap(errNotFound, err)
}
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err != nil {
return nil, errors.Wrap(errOpenFile, err)
}
defer file.Close()
reader := csv.NewReader(file)
nodes := []Node{}
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrap(errReadFile, err)
}
if len(l) < columns {
return nil, errEmptyLine
}
nodes = append(nodes, Node{l[0], l[1]})
}
return nodes, nil
}
-5
View File
@@ -1,5 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package opcua contains OPC-UA server implementation.
package opcua
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package events provides the domain concept definitions needed to support
// opcua events functionality.
package events
-27
View File
@@ -1,27 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package events
type createThingEvent struct {
id string
opcuaNodeID string
}
type removeThingEvent struct {
id string
}
type connectThingEvent struct {
chanID string
thingIDs []string
}
type createChannelEvent struct {
id string
opcuaServerURI string
}
type removeChannelEvent struct {
id string
}
-62
View File
@@ -1,62 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package events
import (
"context"
"fmt"
"github.com/absmach/magistrala/opcua"
"github.com/go-redis/redis/v8"
)
var _ opcua.RouteMapRepository = (*routerMap)(nil)
type routerMap struct {
client *redis.Client
prefix string
}
// NewRouteMapRepository returns redis thing cache implementation.
func NewRouteMapRepository(client *redis.Client, prefix string) opcua.RouteMapRepository {
return &routerMap{
client: client,
prefix: prefix,
}
}
func (mr *routerMap) Save(ctx context.Context, mgxID, opcuaID string) error {
tkey := fmt.Sprintf("%s:%s", mr.prefix, mgxID)
if err := mr.client.Set(ctx, tkey, opcuaID, 0).Err(); err != nil {
return err
}
lkey := fmt.Sprintf("%s:%s", mr.prefix, opcuaID)
if err := mr.client.Set(ctx, lkey, mgxID, 0).Err(); err != nil {
return err
}
return nil
}
func (mr *routerMap) Get(ctx context.Context, opcuaID string) (string, error) {
lKey := fmt.Sprintf("%s:%s", mr.prefix, opcuaID)
mval, err := mr.client.Get(ctx, lKey).Result()
if err != nil {
return "", err
}
return mval, nil
}
func (mr *routerMap) Remove(ctx context.Context, mgxID string) error {
mkey := fmt.Sprintf("%s:%s", mr.prefix, mgxID)
lval, err := mr.client.Get(ctx, mkey).Result()
if err != nil {
return err
}
lkey := fmt.Sprintf("%s:%s", mr.prefix, lval)
return mr.client.Del(ctx, mkey, lkey).Err()
}

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