mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 07:10:19 +00:00
MG-2187 - Simplify Magistrala core repository (#2338)
Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 := ¬ifier{
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)`
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}()
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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", ""),
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package mocks contains mocks for testing purposes.
|
||||
package mocks
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package opcua contains OPC-UA server implementation.
|
||||
package opcua
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user