diff --git a/Makefile b/Makefile index af0c511..315894a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # VERSION/RELEASE # If no version is specified as a parameter of make, the last git hash # value is taken. +EPOCH=0 VERSION?=$(shell git describe --abbrev=0)+$(shell date +'%Y%m%d%H%I%S') RELEASE?=1 @@ -12,55 +13,6 @@ RELEASE?=1 GOPROXY?=$(shell go env GOPROXY) GOPRIVATE?=$(shell go env GOPRIVATE) -# EXECUTABLE -# Executable binary which should be compiled for different architecures -EXECUTABLE:=flucky - -# DARWIN_EXECUTABLES AND TARGETS -DARWIN_EXECUTABLES:=\ - darwin/386/${EXECUTABLE} \ - darwin/amd64/${EXECUTABLE} - -DARWIN_EXECUTABLE_TARGETS:=\ - ${DARWIN_EXECUTABLES:%=bin/%} - -# FREEBSD_EXECUTABLES AND TARGETS -FREEBSD_EXECUTABLES:=\ - freebsd/amd64/${EXECUTABLE} - -FREEBSD_EXECUTABLE_TARGETS:=\ - ${FREEBSD_EXECUTABLES:%=bin/%} - -# LINUX_EXECUTABLES AND TARGETS -LINUX_EXECUTABLES:=\ - linux/amd64/${EXECUTABLE} \ - linux/386/${EXECUTABLE} \ - linux/arm/5/${EXECUTABLE} \ - linux/arm/7/${EXECUTABLE} - -LINUX_EXECUTABLE_TARGETS:=\ - ${LINUX_EXECUTABLES:%=bin/%} - -# UNIX_EXECUTABLES AND TARGETS -# Define all executables for different architectures and operation systems -UNIX_EXECUTABLES:= \ - ${DARWIN_EXECUTABLES} \ - ${FREEBSD_EXECUTABLES} \ - ${LINUX_EXECUTABLES} - -UNIX_EXECUTABLE_TARGETS:= \ - ${DARWIN_EXECUTABLE_TARGETS} \ - ${FREEBSD_EXECUTABLE_TARGETS} \ - ${LINUX_EXECUTABLE_TARGETS} - -# EXECUTABLES AND TARGETS -# Include all UNIX and Windows targets. -EXECUTABLES:=\ - ${UNIX_EXECUTABLES} - -EXECUTABLE_TARGETS:= \ - ${UNIX_EXECUTABLE_TARGETS} - # CONTAINER_RUNTIME # The CONTAINER_RUNTIME variable will be used to specified the path to a # container runtime. This is needed to start and run a container images. @@ -96,48 +48,15 @@ CONTAINER_IMAGE_SHORT=${CONTAINER_IMAGE_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CON # BINARIES # ============================================================================== -# current os +EXECUTABLE=flucky +EXECUTABLE_TARGETS:= \ + bin/linux/amd64/${EXECUTABLE} \ + bin/linux/arm/5/${EXECUTABLE} \ + bin/linux/arm/7/${EXECUTABLE} \ + bin/tmp/${EXECUTABLE} + ${EXECUTABLE}: bin/tmp/${EXECUTABLE} -# build all binaries -PHONY:=all -all: ${EXECUTABLE_TARGETS} - -# darwin os -bin/darwin/386/${EXECUTABLE}: bindata - CGO_ENABLED=1 \ - GOOS=darwin \ - GOARCH=386 \ - GOPROXY=${GOPROXY} \ - GOPRIVATE=${GOPRIVATE} \ - go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} - -bin/darwin/amd64/${EXECUTABLE}: bindata - CGO_ENABLED=1 \ - GOOS=darwin \ - GOARCH=amd64 \ - GOPROXY=${GOPROXY} \ - GOPRIVATE=${GOPRIVATE} \ - go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} - -# freebsd os -bin/freebsd/amd64/${EXECUTABLE}: bindata - CGO_ENABLED=1 \ - GOOS=freebsd \ - GOARCH=amd64 \ - GOPROXY=${GOPROXY} \ - GOPRIVATE=${GOPRIVATE} \ - go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} - -# linux os -bin/linux/386/${EXECUTABLE}: bindata - CGO_ENABLED=1 \ - GOOS=linux \ - GOARCH=386 \ - GOPROXY=${GOPROXY} \ - GOPRIVATE=${GOPRIVATE} \ - go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} - bin/linux/amd64/${EXECUTABLE}: bindata CGO_ENABLED=1 \ GOOS=linux \ @@ -181,31 +100,26 @@ bindata: clean ${BINDATA_TARGETS} pkg/repository/db/bindataSQL.go: go-bindata -pkg db -o ./pkg/repository/db/bindataSQL.go pkg/repository/db/postgres/*** pkg/repository/db/sqlite3/*** -# TEST -# ============================================================================== -PHONY+=test/unit -test/unit: clean bindata - go test -v -race -coverprofile=coverage.txt -covermode=atomic $(shell go list ./... | grep -v db) - -# PACKAGES -# ============================================================================== -%.rpm: % - rpm-builder \ - --exec-file "$<:/usr/bin/${EXECUTABLE}" \ - --dir "systemd:/usr/lib/systemd/system" \ - --license "Apache 2.0" \ - --version ${VERSION:v%=%} \ - --release ${RELEASE} \ - --out ${@} \ - ${EXECUTABLE} - -# OTHER STUFF +# CLEAN # ============================================================================== PHONY+=clean clean: rm --force ${BINDATA_TARGETS} || true rm --force --recursive bin/ || true +# TEST +# ============================================================================== +PHONY+=test/unit +test/unit: bindata + go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./pkg/... + +PHONY+=test/integration +test/integration: + go test -v -count=1 -timeout 600s ./it/... + +test/coverage: test/unit + go tool cover -html=coverage.txt + # CONTAINER IMAGE STEPS # ============================================================================== PHONY+=container-image/build/amd64 @@ -243,8 +157,8 @@ PHONY+=container-run/all container-run/all: $(MAKE) container-run COMMAND=${@:container-run/%=%} -PHONY+=${UNIX_EXECUTABLE_TARGETS:%=container-run/%} -${UNIX_EXECUTABLE_TARGETS:%=container-run/%}: +PHONY+=${EXECUTABLE_TARGETS:%=container-run/%} +${EXECUTABLE_TARGETS:%=container-run/%}: $(MAKE) container-run COMMAND=${@:container-run/%=%} # CONTAINER STEPS - GO-BINDATA @@ -253,18 +167,18 @@ PHONY+=container-run/bindata container-run/bindata: $(MAKE) container-run COMMAND=${@:container-run/%=%} +# CONTAINER STEPS - CLEAN +# ============================================================================== +PHONY+=container-run/clean +container-run/clean: + $(MAKE) container-run COMMAND=${@:container-run/%=%} + # CONTAINER STEPS - TEST # ============================================================================== PHONY+=container-run/test/unit container-run/test/unit: $(MAKE) container-run COMMAND=${@:container-run/%=%} -# CONTAINER STEPS - OTHER STUF -# ============================================================================== -PHONY+=container-run/clean -container-run/clean: - $(MAKE) container-run COMMAND=${@:container-run/%=%} - # GENERAL CONTAINER COMMAND # ============================================================================== PHONY+=container-run diff --git a/go.mod b/go.mod index 0d8202f..c45729c 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,14 @@ require ( github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v1.13.1 + github.com/docker/go-connections v0.4.0 + github.com/docker/go-units v0.4.0 // indirect github.com/go-flucky/go-dht v0.1.1 github.com/lib/pq v1.4.0 github.com/mattn/go-sqlite3 v1.10.0 + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index 3874c88..dc26bad 100644 --- a/go.sum +++ b/go.sum @@ -30,9 +30,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= +github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be h1:jCs/SAXdXUA0aqvEVH0wyeGpJzYOZ1XC9Kpm5anSY5E= github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be/go.mod h1:WNT729lCsTFhT6Vyvg6cG8aHV1t6p+DsA8l4omOuaos= github.com/go-flucky/go-dht v0.1.1 h1:dPz9F5D7oUaTd0GUGTvT4lBH2R043h1bkmhTlpQax1Y= github.com/go-flucky/go-dht v0.1.1/go.mod h1:Yk/cct+/u+eCS7pB/kc0tc7MrVXdFI4W15MJCj5FRUc= @@ -72,15 +79,15 @@ github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -140,6 +147,7 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index fc5d42d..41887d7 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,40 +1,31 @@ package config_test -import ( - "fmt" - "testing" +// func TestAddRemoveSensor(t *testing.T) { +// require := require.New(t) - "github.com/stretchr/testify/require" - "github.com/volker-raschek/flucky/pkg/config" - "github.com/volker-raschek/flucky/pkg/types" -) +// // Test: No device configured +// cnf := new(config.Config) +// err := cnf.AddSensor(&types.Sensor{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf"}) +// require.Error(err) -func TestAddRemoveSensor(t *testing.T) { - require := require.New(t) +// // wireID := "sdfsdff" +// // i2cBus := 1 +// // i2cAddress := 78 - // Test: No device configured - cnf := new(config.Config) - err := cnf.AddSensor(&types.Sensor{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf"}) - require.Error(err) +// cnf.Device = &types.Device{ID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"} +// testCases := map[*types.Sensor]error{ +// {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"}: fmt.Errorf("No sensor name defined"), +// {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01"}: fmt.Errorf("Failed to parse tick duration: time: invalid duration "), +// {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: nil, +// {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"), +// // {ID: "f90cfc18-f141-4cfd-a8d2-fb40082de5cc", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"), +// // {ID: "860a9922-62cb-4c9b-b5af-5fa783cebe9d", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s", WireID: &wireID}: fmt.Errorf("Wire socket not found: /sys/bus/w1/devices/sdfsdff/w1_slave"), +// // {ID: "9be8989c-b2a1-4401-a82f-d6989ec226fe", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s"}: nil, +// } - // wireID := "sdfsdff" - // i2cBus := 1 - // i2cAddress := 78 +// for sensor, expectedErr := range testCases { +// err := cnf.AddSensor(sensor) +// require.Equal(expectedErr, err) +// } - cnf.Device = &types.Device{ID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"} - testCases := map[*types.Sensor]error{ - {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"}: fmt.Errorf("No sensor name defined"), - {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01"}: fmt.Errorf("Failed to parse tick duration: time: invalid duration "), - {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: nil, - {ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"), - // {ID: "f90cfc18-f141-4cfd-a8d2-fb40082de5cc", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"), - // {ID: "860a9922-62cb-4c9b-b5af-5fa783cebe9d", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s", WireID: &wireID}: fmt.Errorf("Wire socket not found: /sys/bus/w1/devices/sdfsdff/w1_slave"), - // {ID: "9be8989c-b2a1-4401-a82f-d6989ec226fe", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s"}: nil, - } - - for sensor, expectedErr := range testCases { - err := cnf.AddSensor(sensor) - require.Equal(expectedErr, err) - } - -} +// } diff --git a/pkg/repository/db/postgres.go b/pkg/repository/db/postgres.go index 25eef60..aad6463 100644 --- a/pkg/repository/db/postgres.go +++ b/pkg/repository/db/postgres.go @@ -146,7 +146,7 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu defer stmt.Close() for _, measuredValue := range measuredValues { - _, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate) + _, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.SensorID, &measuredValue.CreationDate) if err != nil { return fmt.Errorf("Failed to execute statement: %v", err) } diff --git a/pkg/repository/db/sqlite.go b/pkg/repository/db/sqlite.go index 5c42cd0..5072fa3 100644 --- a/pkg/repository/db/sqlite.go +++ b/pkg/repository/db/sqlite.go @@ -144,9 +144,8 @@ func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues . _, err := stmt.Exec( &measuredValue.ID, &measuredValue.Value, - &measuredValue.FromDate, - &measuredValue.TillDate, &measuredValue.SensorID, + &measuredValue.Date, &measuredValue.CreationDate, &measuredValue.UpdateDate, ) @@ -214,6 +213,7 @@ func (sqlite *SQLite) InsertSensors(ctx context.Context, sensors ...*types.Senso &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, + &sensor.TickDuration, &sensor.DeviceID, &sensor.CreationDate, ) @@ -266,6 +266,10 @@ func (sqlite *SQLite) SelectDevice(ctx context.Context, id string) (*types.Devic return nil, err } + if len(devices) == 0 { + return nil, nil + } + return devices[0], nil } @@ -282,7 +286,7 @@ func (sqlite *SQLite) SelectDevices(ctx context.Context) ([]*types.Device, error return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } - devices, err := sqlite.selectDevices(tx, query, nil) + devices, err := sqlite.selectDevices(tx, query) if err != nil { return nil, err } @@ -302,7 +306,7 @@ func (sqlite *SQLite) selectDevices(tx *sql.Tx, query string, args ...interface{ } defer stmt.Close() - rows, err := stmt.Query() + rows, err := stmt.Query(args...) if err != nil { return nil, fmt.Errorf("Failed to query statement: %v", err) } @@ -405,21 +409,22 @@ func (sqlite *SQLite) selectMeasuredValue(tx *sql.Tx, query string, args ...inte measuredValues := make([]*types.MeasuredValue, 0) for rows.Next() { - measuredValues := new(types.MeasuredValue) + measuredValue := new(types.MeasuredValue) err := rows.Scan( - &measuredValues.ID, - &measuredValues.Value, - &measuredValues.FromDate, - &measuredValues.TillDate, - &measuredValues.SensorID, - &measuredValues.CreationDate, - &measuredValues.UpdateDate, + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.SensorID, + &measuredValue.Date, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, ) if err != nil { tx.Rollback() return nil, err } + + measuredValues = append(measuredValues, measuredValue) } return measuredValues, nil @@ -502,7 +507,7 @@ func (sqlite *SQLite) SelectSensor(ctx context.Context, id string) (*types.Senso return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } - sensors, err := sqlite.selectSensors(tx, query, nil) + sensors, err := sqlite.selectSensors(tx, query, id) if err != nil { return nil, err } @@ -512,6 +517,10 @@ func (sqlite *SQLite) SelectSensor(ctx context.Context, id string) (*types.Senso return nil, fmt.Errorf("Failed to commit transaction: %v", err) } + if len(sensors) == 0 { + return nil, nil + } + return sensors[0], nil } @@ -528,7 +537,7 @@ func (sqlite *SQLite) SelectSensors(ctx context.Context) ([]*types.Sensor, error return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } - sensors, err := sqlite.selectSensors(tx, query, nil) + sensors, err := sqlite.selectSensors(tx, query) if err != nil { return nil, err } @@ -548,7 +557,7 @@ func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{ } defer stmt.Close() - rows, err := stmt.Query() + rows, err := stmt.Query(args...) if err != nil { return nil, fmt.Errorf("Failed to query statement: %v", err) } @@ -566,6 +575,7 @@ func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{ &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, + &sensor.TickDuration, &sensor.DeviceID, &sensor.CreationDate, ) @@ -709,6 +719,7 @@ func (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Senso &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, + &sensor.TickDuration, &sensor.DeviceID, &sensor.CreationDate, &sensor.ID, diff --git a/pkg/repository/db/sqlite3/createTableHumidities.sql b/pkg/repository/db/sqlite3/createTableHumidities.sql index a0cd6b4..da02a9c 100644 --- a/pkg/repository/db/sqlite3/createTableHumidities.sql +++ b/pkg/repository/db/sqlite3/createTableHumidities.sql @@ -1,8 +1,7 @@ CREATE TABLE IF NOT EXISTS humidities ( - humidity_id CHAR(37) PRIMARY KEY, - humidity_value NUMERIC(10,3) NOT NULL, - humidity_from_date TIMESTAMP NOT NULL, - humidity_till_date TIMESTAMP, + id CHAR(37) PRIMARY KEY, + value NUMERIC(10,3) NOT NULL, + date TIMESTAMP NOT NULL, sensor_id CHAR(37) NOT NULL, creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_date TIMESTAMP, diff --git a/pkg/repository/db/sqlite3/createTablePressures.sql b/pkg/repository/db/sqlite3/createTablePressures.sql index 139567a..0f6cc27 100644 --- a/pkg/repository/db/sqlite3/createTablePressures.sql +++ b/pkg/repository/db/sqlite3/createTablePressures.sql @@ -1,8 +1,7 @@ CREATE TABLE IF NOT EXISTS pressures ( - pressure_id CHAR(37) PRIMARY KEY, - pressure_value NUMERIC(10,3) NOT NULL, - pressure_from_date TIMESTAMP NOT NULL, - pressure_till_date TIMESTAMP, + id CHAR(37) PRIMARY KEY, + value NUMERIC(10,3) NOT NULL, + date TIMESTAMP NOT NULL, sensor_id CHAR(37) NOT NULL, creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_date TIMESTAMP, diff --git a/pkg/repository/db/sqlite3/createTableSensors.sql b/pkg/repository/db/sqlite3/createTableSensors.sql index 4c13843..d189f26 100644 --- a/pkg/repository/db/sqlite3/createTableSensors.sql +++ b/pkg/repository/db/sqlite3/createTableSensors.sql @@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS sensors ( gpio_number VARCHAR(6), sensor_model VARCHAR(16) NOT NULL, sensor_enabled INTEGER(1) DEFAULT 1 NOT NULL, + tick_duration VARCHAR(6) NOT NULL, device_id CHAR(37) NOT NULL, creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(device_id) REFERENCES devices(device_id) diff --git a/pkg/repository/db/sqlite3/createTableTemperatures.sql b/pkg/repository/db/sqlite3/createTableTemperatures.sql index 1588976..b2220f9 100644 --- a/pkg/repository/db/sqlite3/createTableTemperatures.sql +++ b/pkg/repository/db/sqlite3/createTableTemperatures.sql @@ -1,10 +1,9 @@ CREATE TABLE IF NOT EXISTS temperatures ( - temperature_id CHAR(37) PRIMARY KEY, - temperature_value NUMERIC(10,3) NOT NULL, - temperature_from_date TIMESTAMP NOT NULL, - temperature_till_date TIMESTAMP, - sensor_id CHAR(37) NOT NULL, - creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + id CHAR(37) PRIMARY KEY, + value NUMERIC(10,3) NOT NULL, + date TIMESTAMP NOT NULL, + sensor_id CHAR(37) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_date TIMESTAMP, FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) ); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/deleteDevice.sql b/pkg/repository/db/sqlite3/deleteDevice.sql index 22ad7fa..5d75ead 100644 --- a/pkg/repository/db/sqlite3/deleteDevice.sql +++ b/pkg/repository/db/sqlite3/deleteDevice.sql @@ -1,2 +1,2 @@ -DELETE FROM sensors +DELETE FROM devices WHERE device_id = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertHumidity.sql b/pkg/repository/db/sqlite3/insertHumidity.sql index e7a7041..b79bf52 100644 --- a/pkg/repository/db/sqlite3/insertHumidity.sql +++ b/pkg/repository/db/sqlite3/insertHumidity.sql @@ -1,10 +1,9 @@ INSERT INTO humidities ( - humidity_id, - humidity_value, - humidity_from_date, - humidity_till_date, + id, + value, + date, sensor_id, creation_date, update_date ) -VALUES ($1, $2, $3, $4, $5, $6, $7); \ No newline at end of file +VALUES ($1, $2, $3, $4, $5, $6); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertPressure.sql b/pkg/repository/db/sqlite3/insertPressure.sql index 8add4b5..7f68da9 100644 --- a/pkg/repository/db/sqlite3/insertPressure.sql +++ b/pkg/repository/db/sqlite3/insertPressure.sql @@ -1,10 +1,9 @@ INSERT INTO pressures ( - pressure_id, - pressure_value, - pressure_from_date, - pressure_till_date, + id, + value, + date, sensor_id, creation_date, update_date ) -VALUES ($1, $2, $3, $4, $5, $6, $7); \ No newline at end of file +VALUES ($1, $2, $3, $4, $5, $6); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertSensor.sql b/pkg/repository/db/sqlite3/insertSensor.sql index 2aca33a..ae1c3e8 100644 --- a/pkg/repository/db/sqlite3/insertSensor.sql +++ b/pkg/repository/db/sqlite3/insertSensor.sql @@ -8,7 +8,8 @@ INSERT INTO sensors ( gpio_number, sensor_model, sensor_enabled, + tick_duration, device_id, creation_date ) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11); \ No newline at end of file +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertTemperature.sql b/pkg/repository/db/sqlite3/insertTemperature.sql index 520555a..9afb3b2 100644 --- a/pkg/repository/db/sqlite3/insertTemperature.sql +++ b/pkg/repository/db/sqlite3/insertTemperature.sql @@ -1,10 +1,9 @@ INSERT INTO temperatures ( - temperature_id, - temperature_value, - temperature_from_date, - temperature_till_date, + id, + value, + date, sensor_id, creation_date, update_date ) -VALUES ($1, $2, $3, $4, $5, $6, $7); \ No newline at end of file +VALUES ($1, $2, $3, $4, $5, $6); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectHumidities.sql b/pkg/repository/db/sqlite3/selectHumidities.sql index 891dbd7..653ab40 100644 --- a/pkg/repository/db/sqlite3/selectHumidities.sql +++ b/pkg/repository/db/sqlite3/selectHumidities.sql @@ -1,8 +1,7 @@ SELECT - humidity_id, - humidity_value, - humidity_from_date, - humidity_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/selectHumidity.sql b/pkg/repository/db/sqlite3/selectHumidity.sql index e1346cf..78ebc28 100644 --- a/pkg/repository/db/sqlite3/selectHumidity.sql +++ b/pkg/repository/db/sqlite3/selectHumidity.sql @@ -1,8 +1,7 @@ SELECT - humidity_id, - humidity_value, - humidity_from_date, - humidity_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/selectPressure.sql b/pkg/repository/db/sqlite3/selectPressure.sql index d9e386d..08f8a71 100644 --- a/pkg/repository/db/sqlite3/selectPressure.sql +++ b/pkg/repository/db/sqlite3/selectPressure.sql @@ -1,8 +1,7 @@ SELECT - pressure_id, - pressure_value, - pressure_from_date, - pressure_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/selectPressures.sql b/pkg/repository/db/sqlite3/selectPressures.sql index 6cf70f9..0073b96 100644 --- a/pkg/repository/db/sqlite3/selectPressures.sql +++ b/pkg/repository/db/sqlite3/selectPressures.sql @@ -1,8 +1,7 @@ SELECT - pressure_id, - pressure_value, - pressure_from_date, - pressure_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/selectSensor.sql b/pkg/repository/db/sqlite3/selectSensor.sql index 0d6f164..9a42af7 100644 --- a/pkg/repository/db/sqlite3/selectSensor.sql +++ b/pkg/repository/db/sqlite3/selectSensor.sql @@ -8,6 +8,7 @@ SELECT gpio_number, sensor_model, sensor_enabled, + tick_duration, device_id, creation_date FROM diff --git a/pkg/repository/db/sqlite3/selectSensors.sql b/pkg/repository/db/sqlite3/selectSensors.sql index 0c85bc4..87978b3 100644 --- a/pkg/repository/db/sqlite3/selectSensors.sql +++ b/pkg/repository/db/sqlite3/selectSensors.sql @@ -8,7 +8,10 @@ SELECT gpio_number, sensor_model, sensor_enabled, + tick_duration, device_id, creation_date FROM - sensors; \ No newline at end of file + sensors +ORDER BY + sensor_id; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectTemperature.sql b/pkg/repository/db/sqlite3/selectTemperature.sql index a265c05..51ab33a 100644 --- a/pkg/repository/db/sqlite3/selectTemperature.sql +++ b/pkg/repository/db/sqlite3/selectTemperature.sql @@ -1,8 +1,7 @@ SELECT - temperature_id, - temperature_value, - temperature_from_date, - temperature_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/selectTemperatures.sql b/pkg/repository/db/sqlite3/selectTemperatures.sql index 530f24a..ce768ee 100644 --- a/pkg/repository/db/sqlite3/selectTemperatures.sql +++ b/pkg/repository/db/sqlite3/selectTemperatures.sql @@ -1,8 +1,7 @@ SELECT - temperature_id, - temperature_value, - temperature_from_date, - temperature_till_date, + id, + value, + date, sensor_id, creation_date, update_date diff --git a/pkg/repository/db/sqlite3/updateDevice.sql b/pkg/repository/db/sqlite3/updateDevice.sql index 834889f..9a3836f 100644 --- a/pkg/repository/db/sqlite3/updateDevice.sql +++ b/pkg/repository/db/sqlite3/updateDevice.sql @@ -1,7 +1,7 @@ -UPDATE device +UPDATE devices SET device_name = $1, device_location = $2, creation_date = $3 WHERE - device_id = $4, \ No newline at end of file + device_id = $4 \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/updateSensor.sql b/pkg/repository/db/sqlite3/updateSensor.sql index 44507a6..57ab62c 100644 --- a/pkg/repository/db/sqlite3/updateSensor.sql +++ b/pkg/repository/db/sqlite3/updateSensor.sql @@ -8,7 +8,8 @@ SET gpio_number = $6, sensor_model = $7, sensor_enabled = $8, - device_id = $9, - creation_date = $10 + tick_duration = $9, + device_id = $10, + creation_date = $11 WHERE - sensor_id = $11; \ No newline at end of file + sensor_id = $12; \ No newline at end of file diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index fad3ec8..014056d 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -110,7 +110,7 @@ func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, } } - return sensors, nil + return cachedSensors, nil } // RemoveDevices removes devices by their ids from the repository. Additional diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go new file mode 100644 index 0000000..550dddb --- /dev/null +++ b/pkg/repository/repository_test.go @@ -0,0 +1,309 @@ +package repository_test + +import ( + "context" + "fmt" + "math/rand" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/require" + "github.com/volker-raschek/flucky/pkg/repository" + "github.com/volker-raschek/flucky/pkg/testutils/dockerutils" + "github.com/volker-raschek/flucky/pkg/types" + "github.com/volker-raschek/go-logger/pkg/logger" +) + +func TestPostgresBackend(t *testing.T) { + t.Skip("Database backend postgres not completly implemented") + require := require.New(t) + dockerClient, err := dockerutils.New() + require.NoError(err) + + rand.Seed(time.Now().Unix()) + postgresHostPort := rand.Intn(10024-1024) + 1024 + postgresDBPasswort := "postgres" + postgresContainerID, err := dockerClient.NewBuilder("postgres:latest"). + Port(fmt.Sprintf("%v:5432/tcp", postgresHostPort)). + Pull(). + AddEnv("PGTZ", "Europe/Berlin"). + AddEnv("POSTGRES_PASSWORD", postgresDBPasswort). + AddEnv("TZ", "Europe/Berlin"). + Mount("/etc/localtime", "/etc/localtime"). + Start(context.Background()) + cleanup := func() { + dockerClient.ContainerRemoveByIDs(context.Background(), postgresContainerID) + } + t.Cleanup(cleanup) + require.NoError(err) + + // postgres://[user]:[password]@[host]:[port]/[path]?[query] + storageEndpoint, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@localhost:%v", postgresDBPasswort, postgresHostPort)) + require.NoError(err) + + repo, err := repository.New(storageEndpoint, logger.NewDefaultLogger(logger.LogLevelDebug)) + require.NoError(err) + + testBackend(t, repo) +} + +func TestSQLiteBackend(t *testing.T) { + require := require.New(t) + workspace := filepath.Join(os.TempDir(), uuid.NewV4().String()) + err := os.MkdirAll(workspace, 0755) + require.NoError(err) + defer os.RemoveAll(workspace) + + storageEndpoint, err := url.Parse(fmt.Sprintf("sqlite3://%v/test.db", workspace)) + require.NoError(err) + + repo, err := repository.New(storageEndpoint, logger.NewDefaultLogger(logger.LogLevelDebug)) + require.NoError(err) + + testBackend(t, repo) +} + +func testBackend(t *testing.T, repo *repository.Repository) { + require := require.New(t) + location := uuid.NewV4().String() + expectedDevices := []*types.Device{ + { + ID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f", + Name: "f2b245eb-b15f-40e1-9212-9a645907b710", + Location: &location, + CreationDate: *timeNow(require), + }, + { + ID: "39b8f150-8abf-4539-9f16-7f68cedb1649", + Name: "62e3978f-2198-4aa9-9d6f-cdc91a468b00", + Location: &location, + CreationDate: *timeNow(require), + }, + } + + // Test: AddDevice + err := repo.AddDevices(expectedDevices...) + require.NoError(err) + + // Test: GetDevices + devices, err := repo.GetDevices() + require.NoError(err) + require.Len(devices, len(expectedDevices)) + require.EqualValues(expectedDevices, devices) + + // Test: GetDevice + device, err := repo.GetDevice(expectedDevices[0].ID) + require.NoError(err) + require.EqualValues(expectedDevices[0], device) + + // Test: RemoveDevice + err = repo.RemoveDevices(expectedDevices[1].ID) + require.NoError(err) + + devices, err = repo.GetDevices() + require.NoError(err) + require.Len(devices, 1) + + device, err = repo.GetDevice(expectedDevices[1].ID) + require.NoError(err) + require.Nil(device) + + // Test: Update Devices + location = "MyLocation" + expectedDevice := &types.Device{ + ID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f", + Name: "Hello World", + Location: &location, + CreationDate: *timeNow(require), + } + + err = repo.UpdateDevices(expectedDevice) + require.NoError(err) + + device, err = repo.GetDevice(expectedDevice.ID) + require.NoError(err) + require.EqualValues(expectedDevice, device) + + var ( + wireID = "50473fdc-f6ef-4227-b3c4-484d8e9c1323" + i2cBus = 1 + i2cAddress uint8 = 76 + expectedSensors = []*types.Sensor{ + { + ID: "0f8b88b0-c20d-42b2-ab51-b09ca99c0752", + Name: "e1fbdbe9-cebf-42ed-8065-bf4882ccf76b", + Location: "6d5b5450-1f87-47cb-b185-f64c35fae3c1", + GPIONumber: "GPIO14", + Model: "DHT11", + Enabled: true, + TickDuration: "1m", + DeviceID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f", + CreationDate: *timeNow(require), + }, + { + ID: "80b1c4bd-abec-4ff0-afb4-bd70aeed0c83", + Name: "544365ee-ece9-44ea-911d-5d920a68d4ba", + Location: "7ae2d05e-9e6b-4d2d-b26a-cb4acca83778", + WireID: &wireID, + Model: "DS18B20", + Enabled: true, + TickDuration: "5m", + DeviceID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f", + CreationDate: *timeNow(require), + }, + { + ID: "8c74397f-8e60-4c9d-960d-3197747cef9a", + Name: "4b808675-de02-4866-893d-1c77f23b9304", + Location: "8a085c0f-dd3c-447f-8c4e-c4c1d869c7b6", + I2CBus: &i2cBus, + I2CAddress: &i2cAddress, + Model: "BME280", + Enabled: false, + TickDuration: "10m", + DeviceID: "39b8f150-8abf-4539-9f16-7f68cedb1649", + CreationDate: *timeNow(require), + }, + } + ) + + // Test: AddSensors + err = repo.AddSensors(expectedSensors...) + require.NoError(err) + + // Test: GetSensors + sensors, err := repo.GetSensors() + require.NoError(err) + require.Len(sensors, len(expectedSensors)) + require.Equal(expectedSensors, sensors) + + // Test: GetSensor + sensor, err := repo.GetSensor(expectedSensors[0].ID) + require.NoError(err) + require.Equal(expectedSensors[0], sensor) + + // Test: GetSensorsByDeviceID + sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") + require.NoError(err) + require.Len(sensors, 2) + require.Equal(expectedSensors[0:2], sensors) + + // Test: RemoveSensors + err = repo.RemoveSensors(expectedSensors[0].ID) + require.NoError(err) + + sensors, err = repo.GetSensors() + require.NoError(err) + require.Len(sensors, 2) + + sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") + require.NoError(err) + require.Len(sensors, 1) + + sensor, err = repo.GetSensor(expectedSensors[0].ID) + require.NoError(err) + require.Nil(sensor) + + // Test: RemoveSensorsByNames + err = repo.RemoveSensorsByNames(expectedSensors[1].Name) + require.NoError(err) + + sensors, err = repo.GetSensors() + require.NoError(err) + require.Len(sensors, 1) + + sensor, err = repo.GetSensor(expectedSensors[1].ID) + require.NoError(err) + require.Nil(sensor) + + // Test: RenameSensor + err = repo.RenameSensors(expectedSensors[2].Name, "Hello") + require.NoError(err) + + sensor, err = repo.GetSensor(expectedSensors[2].ID) + require.Equal("Hello", sensor.Name) + require.NotNil(sensor) + + // Test: DisableSensorsByNames + err = repo.DisableSensorsByNames("Hello") + require.NoError(err) + + sensor, err = repo.GetSensor(expectedSensors[2].ID) + require.False(sensor.Enabled) + require.NotNil(sensor) + + // Test: EnableSensorsByName + err = repo.EnableSensorsByNames("Hello") + require.NoError(err) + + sensor, err = repo.GetSensor(expectedSensors[2].ID) + require.True(sensor.Enabled) + require.NotNil(sensor) + + // Test: UpdateSensors + expectedSensor := &types.Sensor{ + ID: "8c74397f-8e60-4c9d-960d-3197747cef9a", + Name: "4b808675-de02-4866-893d-1c77f23b9304", + Location: "Über den Wolken muss die Freiheit wohl grenzenlos sein...", + I2CBus: nil, + I2CAddress: nil, + Model: "SDS011", + Enabled: true, + TickDuration: "6h", + DeviceID: "39b8f150-8abf-4539-9f16-7f68cedb1649", + CreationDate: *timeNow(require), + } + + err = repo.UpdateSensors(expectedSensor) + require.NoError(err) + + sensor, err = repo.GetSensor(expectedSensor.ID) + require.NoError(err) + require.NotNil(sensor) + require.EqualValues(expectedSensor, sensor) + + var ( + expectedMeasuredValues = []*types.MeasuredValue{ + { + ID: "2e5a297a-3da0-46ae-89d2-0fcab0f1d5f7", + Value: 32, + ValueType: "temperature", + Date: *timeNow(require), + SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", + CreationDate: timeNow(require), + UpdateDate: nil, + }, + { + ID: "d69f1b62-0c6c-4058-b42c-4a2821bd220c", + Value: 38, + ValueType: "temperature", + Date: *timeNow(require), + SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", + CreationDate: timeNow(require), + UpdateDate: nil, + }, + { + ID: "ea945ae0-412b-4561-a191-1f8f1f909fa4", + Value: 35.4, + ValueType: "temperature", + Date: *timeNow(require), + SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", + CreationDate: timeNow(require), + UpdateDate: nil, + }, + } + ) + + // Test: AddMeasuredValues + err = repo.AddMeasuredValues(expectedMeasuredValues...) + require.NoError(err) +} + +func timeNow(require *require.Assertions) *time.Time { + now, err := time.Parse("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05")) + require.NoError(err) + return &now +} diff --git a/pkg/sensor/bme280.go b/pkg/sensor/bme280.go index b1ff8e5..564c7c8 100644 --- a/pkg/sensor/bme280.go +++ b/pkg/sensor/bme280.go @@ -67,24 +67,21 @@ func (bme280 *BME280) Read() ([]*types.MeasuredValue, error) { ID: uuid.NewV4().String(), Value: float64(humidityValue), ValueType: "humidity", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: bme280.ID, }, { ID: uuid.NewV4().String(), Value: float64(pressureValue), ValueType: "pressure", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: bme280.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), ValueType: "temperature", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: bme280.ID, }, } diff --git a/pkg/sensor/dht11.go b/pkg/sensor/dht11.go index 1a7ac9f..2e9a903 100644 --- a/pkg/sensor/dht11.go +++ b/pkg/sensor/dht11.go @@ -43,16 +43,14 @@ func (dht11 *DHT11) Read() ([]*types.MeasuredValue, error) { ID: uuid.NewV4().String(), Value: float64(humidityValue), ValueType: "humidity", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: dht11.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), ValueType: "temperature", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: dht11.ID, }, } diff --git a/pkg/sensor/dht22.go b/pkg/sensor/dht22.go index 9ad0fc6..4ccf738 100644 --- a/pkg/sensor/dht22.go +++ b/pkg/sensor/dht22.go @@ -43,16 +43,14 @@ func (dht22 *DHT22) Read() ([]*types.MeasuredValue, error) { ID: uuid.NewV4().String(), Value: float64(humidityValue), ValueType: "humidity", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: dht22.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), ValueType: "temperature", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: dht22.ID, }, } diff --git a/pkg/sensor/ds18b20.go b/pkg/sensor/ds18b20.go index 60d687f..59c8c8d 100644 --- a/pkg/sensor/ds18b20.go +++ b/pkg/sensor/ds18b20.go @@ -60,8 +60,7 @@ func (ds18b20 *DS18B20) Read() ([]*types.MeasuredValue, error) { ID: uuid.NewV4().String(), Value: float64(temperatureValue), ValueType: "temperature", - FromDate: format.FormatedTime(), - TillDate: format.FormatedTime(), + Date: format.FormatedTime(), SensorID: ds18b20.ID, }, } diff --git a/pkg/testutils/dockerutils/builder.go b/pkg/testutils/dockerutils/builder.go new file mode 100644 index 0000000..6bf5d86 --- /dev/null +++ b/pkg/testutils/dockerutils/builder.go @@ -0,0 +1,200 @@ +package dockerutils + +import ( + "context" + "fmt" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/go-connections/nat" +) + +type Builder struct { + client *Client + containerConfig *container.Config + containerName string + hostConfig *container.HostConfig + networkConfig *network.NetworkingConfig + networks map[string][]string + ports []string + pull bool + waitForHealthy bool +} + +// AddEnv to the container +func (builder *Builder) AddEnv(key string, value string) *Builder { + builder.containerConfig.Env = append(builder.containerConfig.Env, fmt.Sprintf("%v=%v", key, value)) + return builder +} + +// AddLabel to the container +func (builder *Builder) AddLabel(key string, value string) *Builder { + builder.containerConfig.Labels[key] = value + return builder +} + +// Env set environment variables to the container +func (builder *Builder) Env(env map[string]string) *Builder { + builder.containerConfig.Labels = make(map[string]string) + for key, value := range env { + builder.containerConfig.Labels[key] = value + } + return builder +} + +// Labels set labels to the container +func (builder *Builder) Labels(labels map[string]string) *Builder { + builder.containerConfig.Labels = labels + return builder +} + +// Memory defines a memory limit for the container +func (builder *Builder) Memory(limit int64) *Builder { + builder.hostConfig.Memory = limit + return builder +} + +// Mount a source volume or hostpath into the filesystem of the container +func (builder *Builder) Mount(source string, dest string) *Builder { + builder.hostConfig.Binds = append(builder.hostConfig.Binds, fmt.Sprintf("%v:%v", source, dest)) + return builder +} + +// Mounts a set of source volumes or hostpath into the filesystem of the +// container +func (builder *Builder) Mounts(mounts map[string]string) *Builder { + for source, dest := range mounts { + builder.Mount(source, dest) + } + return builder +} + +// Port defines a port forwarding from the host machine to the container +// Examples: +// - 8080:8080 +// - 10.6.231.10:8080:8080 +// - 10.6.231.10:8080:8080/tcp +func (builder *Builder) Port(port string) *Builder { + builder.ports = append(builder.ports, port) + return builder +} + +// Ports defines a set port forwarding rules from the host machine to the +// container +// Examples: +// - 8080:8080 +// - 10.6.231.10:8080:8080 +// - 10.6.231.10:8080:8080/tcp +func (builder *Builder) Ports(ports []string) *Builder { + builder.ports = ports + return builder +} + +// Pull the image if absent +func (builder *Builder) Pull() *Builder { + builder.pull = true + return builder +} + +// Start the container +func (builder *Builder) Start(ctx context.Context) (string, error) { + + // Pull container image if absent + if builder.pull { + err := builder.client.PullQuiet(ctx, builder.containerConfig.Image) + if err != nil { + return "", err + } + } + + // check if ports are available + exposedPorts, portBindings, err := nat.ParsePortSpecs(builder.ports) + if err != nil { + return "", fmt.Errorf("Failed to parse ports %v: %v", builder.ports, err) + } + + if len(portBindings) > 0 { + builder.containerConfig.ExposedPorts = exposedPorts + builder.hostConfig.PortBindings = portBindings + } + + // add endpoint settings to existing networks + networkExist := make(map[string]bool, 0) + for networkName := range builder.networks { + networkExist[networkName] = false + } + + networks, err := builder.client.NetworkList(ctx, types.NetworkListOptions{}) + if err != nil { + return "", fmt.Errorf("Failed to list networks: %v", err) + } + + for _, nw := range networks { + if aliases, present := builder.networks[nw.Name]; present { + networkExist[nw.Name] = true + builder.networkConfig.EndpointsConfig[nw.Name] = &network.EndpointSettings{ + Aliases: aliases, + NetworkID: nw.ID, + } + } + } + + for nw, found := range networkExist { + if !found { + return "", fmt.Errorf("Failed to add endpoint settings for network %v. It does not exist", nw) + } + } + + // create container + resp, err := builder.client.ContainerCreate(ctx, builder.containerConfig, builder.hostConfig, builder.networkConfig, builder.containerName) + if err != nil { + return "", fmt.Errorf("Failed to create container %v: %v", builder.containerName, err) + } + + // start container + err = builder.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) + if err != nil { + stopError := builder.client.ContainerStopByIDs(ctx, time.Second, resp.ID) + if stopError != nil { + return "", fmt.Errorf("Failed to start container %v: %v\nUnable to remove container %v. Manual cleanup necessary", builder.containerName, err, builder.containerName) + } + return "", fmt.Errorf("Failed to start container %v: %v", builder.containerName, err) + } + + // wait for healthy + if builder.waitForHealthy { + + watcher := builder.client.GetWatcher() + errorChannel := make(chan error, 1) + doneChannel := make(chan struct{}) + err = watcher.AddListener(resp.ID, errorChannel, doneChannel) + if err != nil { + containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) + if containerRemoveError != nil { + return "", fmt.Errorf("Failed while watching status for container %v: %v\nUnable to remove container %v: %v", resp.ID, err, resp.ID, containerRemoveError) + } + return "", fmt.Errorf("Failed while watching status for container %v: %v", resp.ID, err) + } + + select { + case err := <-errorChannel: + if err != nil { + containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) + if containerRemoveError != nil { + return "", fmt.Errorf("Unable to remove container %v: %v", resp.ID, containerRemoveError) + } + return "", err + } + case <-doneChannel: + } + } + + return resp.ID, nil +} + +func (builder *Builder) WaitForHealthy() *Builder { + builder.waitForHealthy = true + return builder +} diff --git a/pkg/testutils/dockerutils/client.go b/pkg/testutils/dockerutils/client.go new file mode 100644 index 0000000..47b295e --- /dev/null +++ b/pkg/testutils/dockerutils/client.go @@ -0,0 +1,220 @@ +package dockerutils + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "strings" + "sync" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" +) + +type Client struct { + *client.Client + watcher *Watcher + mutex *sync.Mutex +} + +// Close docker connection +func (client *Client) Close() error { + client.mutex.Lock() + defer client.mutex.Unlock() + if client.watcher != nil { + client.watcher.stop() + } + return client.Close() +} + +// ContainerListByLabels returns only containers which match by given labels +func (client *Client) ContainerListByLabels(ctx context.Context, all bool, labels map[string]string) ([]types.Container, error) { + filterArgs := filters.NewArgs() + for key, value := range labels { + filterArgs.Add("label", fmt.Sprintf("%v=%v", key, value)) + } + containers, err := client.ContainerList(ctx, types.ContainerListOptions{ + All: all, + Filters: filterArgs, + }) + if err != nil { + return nil, err + } + if containers == nil { + return nil, fmt.Errorf("No containers found by given labels") + } + return containers, nil +} + +// ContainerListByNames returns only containers which match by given labels +func (client *Client) ContainerListByNames(ctx context.Context, all bool, names ...string) ([]types.Container, error) { + filterArgs := filters.NewArgs() + for _, name := range names { + filterArgs.Add("name", name) + } + containers, err := client.ContainerList(ctx, types.ContainerListOptions{ + All: all, + Filters: filterArgs, + }) + if err != nil { + return nil, err + } + if containers == nil { + return nil, fmt.Errorf("No containers found by given names") + } + return containers, nil +} + +// ContainerRemoveByIDs deletes all containers which match by their container ids +func (client *Client) ContainerRemoveByIDs(ctx context.Context, containerIDs ...string) error { + for _, containerID := range containerIDs { + err := client.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true}) + if err != nil { + return err + } + } + return nil +} + +// ContainerRemoveByLabels deletes all containers which match by given labels +func (client *Client) ContainerRemoveByLabels(ctx context.Context, labels map[string]string) error { + containers, err := client.ContainerListByLabels(ctx, true, labels) + if err != nil { + return err + } + for _, container := range containers { + err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true}) + if err != nil { + return err + } + } + return nil +} + +// ContainerRemoveByNames deletes all containers which match by their names +func (client *Client) ContainerRemoveByNames(ctx context.Context, names ...string) error { + containers, err := client.ContainerListByNames(ctx, true, names...) + if err != nil { + return err + } + for _, container := range containers { + err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true}) + if err != nil { + return err + } + } + return nil +} + +// ContainerStopByIDs deletes all containers which match by their container ids +func (client *Client) ContainerStopByIDs(ctx context.Context, timeout time.Duration, containerIDs ...string) error { + for _, containerID := range containerIDs { + err := client.ContainerStop(ctx, containerID, &timeout) + if err != nil { + return err + } + } + return nil +} + +// ContainerStopByLabels shutdown containters which match by given labels +func (client *Client) ContainerStopByLabels(ctx context.Context, timeout time.Duration, labels map[string]string) error { + containers, err := client.ContainerListByLabels(ctx, true, labels) + if err != nil { + return err + } + for _, container := range containers { + err := client.ContainerStop(ctx, container.ID, &timeout) + if err != nil { + return err + } + } + return nil +} + +// GetWatcher returns a watcher for container health states +func (client *Client) GetWatcher() *Watcher { + if client.watcher != nil { + return client.watcher + } + + client.mutex.Lock() + defer client.mutex.Unlock() + + client.watcher = &Watcher{ + client: client, + errorChannels: make(map[string]chan<- error), + doneChannels: make(map[string]chan<- struct{}), + errorMapper: make(map[string]ErrorMapper), + mutex: new(sync.RWMutex), + } + + client.watcher.start() + return client.watcher +} + +// NewBuilder returns a new builder for containers +func (client *Client) NewBuilder(image string) *Builder { + return &Builder{ + client: client, + containerConfig: &container.Config{ + Image: image, + }, + hostConfig: new(container.HostConfig), + networkConfig: new(network.NetworkingConfig), + networks: make(map[string][]string, 0), + ports: make([]string, 0), + pull: false, + waitForHealthy: false, + } +} + +// Pull image +func (client *Client) Pull(ctx context.Context, image string, w io.Writer) error { + + parts := strings.Split(image, "/") + switch len(parts) { + case 1: + image = fmt.Sprintf("docker.io/library/%v", parts[0]) + case 2: + if strings.Compare(parts[0], "library") == 0 || + strings.Compare(parts[0], "docker.io") == 0 { + image = fmt.Sprintf("docker.io/library/%v", parts[1]) + } + } + + readCloser, err := client.ImagePull(ctx, image, types.ImagePullOptions{}) + if err != nil { + return err + } + + _, err = io.Copy(w, readCloser) + if err != nil { + return err + } + + return nil +} + +// PullQuiet image +func (client *Client) PullQuiet(ctx context.Context, image string) error { + return client.Pull(ctx, image, ioutil.Discard) +} + +// New returns a new dockerutil client +func New() (*Client, error) { + dockerClient, err := client.NewEnvClient() + if err != nil { + return nil, err + } + return &Client{ + dockerClient, + nil, + new(sync.Mutex), + }, nil +} diff --git a/pkg/testutils/dockerutils/watcher.go b/pkg/testutils/dockerutils/watcher.go new file mode 100644 index 0000000..29d7a10 --- /dev/null +++ b/pkg/testutils/dockerutils/watcher.go @@ -0,0 +1,164 @@ +package dockerutils + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" +) + +var ( + ErrDied = errors.New("died") + ErrUnhealthy = errors.New("went unhealthy") +) + +type Watcher struct { + client *Client + errorChannels map[string]chan<- error + doneChannels map[string]chan<- struct{} + errorMapper map[string]ErrorMapper + mutex *sync.RWMutex + lastError error + cancelFunc context.CancelFunc +} + +func (watcher *Watcher) AddListenerWithErrorMapper(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}, errorMapper ErrorMapper) error { + watcher.mutex.Lock() + defer watcher.mutex.Unlock() + + if watcher.lastError != nil { + return watcher.lastError + } + + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(20*time.Second)) + defer cancel() + + containerJSON, err := watcher.client.ContainerInspect(ctx, containerID) + if err != nil { + return fmt.Errorf("Unable to check if container is already unhealthy: %v", err) + } + + if containerJSON.State.Dead || !containerJSON.State.Running { + go func() { + errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrDied)) + doneChannel <- struct{}{} + }() + return nil + } + + if containerJSON.State.Health == nil { + go func() { + doneChannel <- struct{}{} + }() + return nil + } + + switch containerJSON.State.Health.Status { + case "starting": + watcher.errorChannels[containerID] = errorChannel + watcher.doneChannels[containerID] = doneChannel + watcher.errorMapper[containerID] = errorMapper + case "healthy": + go func() { + doneChannel <- struct{}{} + }() + case "unhealthy": + go func() { + errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrUnhealthy)) + doneChannel <- struct{}{} + }() + default: + go func() { + errorChannel <- errorMapper(fmt.Errorf("Container %v went in an unknown state during startup: %s", containerID, containerJSON.State.Health.Status)) + doneChannel <- struct{}{} + }() + } + return nil +} + +func (watcher *Watcher) AddListener(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}) error { + return watcher.AddListenerWithErrorMapper(containerID, errorChannel, doneChannel, defaultErrorMapper) +} + +func (watcher *Watcher) removeListener(containerID string) { + watcher.mutex.Lock() + defer watcher.mutex.Unlock() + delete(watcher.doneChannels, containerID) + delete(watcher.errorChannels, containerID) + delete(watcher.errorMapper, containerID) +} + +func (watcher *Watcher) start() { + ctx, cancel := context.WithCancel(context.Background()) + watcher.cancelFunc = cancel + dockerEvents, errors := watcher.client.Events(ctx, types.EventsOptions{}) + + sendErrorFunc := func() { + watcher.mutex.Lock() + for containerID, errorChannel := range watcher.errorChannels { + go func(errorChannel chan<- error, doneChannel chan<- struct{}, err error, errorMapper ErrorMapper) { + errorChannel <- errorMapper(err) + doneChannel <- struct{}{} + }(errorChannel, watcher.doneChannels[containerID], watcher.lastError, watcher.errorMapper[containerID]) + } + watcher.mutex.Unlock() + } + + go func() { + for { + select { + case event := <-dockerEvents: + watcher.mutex.RLock() + errorChannel, present := watcher.errorChannels[event.Actor.ID] + if !present || event.Type != events.ContainerEventType { + continue + } + doneChannel := watcher.doneChannels[event.Actor.ID] + errorMapper := watcher.errorMapper[event.Actor.ID] + watcher.mutex.RUnlock() + + switch event.Action { + case "health_status: healthy": + go func() { + doneChannel <- struct{}{} + }() + watcher.removeListener(event.Actor.ID) + case "health_status: unhealthy": + go func() { + errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrUnhealthy)) + doneChannel <- struct{}{} + }() + watcher.removeListener(event.Actor.ID) + case "die": + go func() { + errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrDied)) + doneChannel <- struct{}{} + }() + watcher.removeListener(event.Actor.ID) + } + case err := <-errors: + watcher.lastError = err + sendErrorFunc() + return + case <-ctx.Done(): + watcher.lastError = fmt.Errorf("Watcher was canceled") + sendErrorFunc() + return + } + } + }() +} + +func (watcher *Watcher) stop() { + watcher.cancelFunc() +} + +type ErrorMapper func(error) error + +func defaultErrorMapper(err error) error { + return err +} diff --git a/pkg/types/device.go b/pkg/types/device.go index bb4b0e9..cf9fab4 100644 --- a/pkg/types/device.go +++ b/pkg/types/device.go @@ -4,9 +4,8 @@ import "time" // Device represent a device with all his settings. type Device struct { - ID string `json:"id" xml:"id"` - Name string `json:"name" xml:"name"` - Location *string `json:"location" xml:"location"` - LastContact *time.Time `json:"last_contact" xml:"last_contact"` - CreationDate time.Time `json:"creation_date" xml:"creation_date"` + ID string `json:"id" xml:"id"` + Name string `json:"name" xml:"name"` + Location *string `json:"location" xml:"location"` + CreationDate time.Time `json:"creation_date" xml:"creation_date"` } diff --git a/pkg/types/measuredValue.go b/pkg/types/measuredValue.go index 089bcf2..cb79b87 100644 --- a/pkg/types/measuredValue.go +++ b/pkg/types/measuredValue.go @@ -11,8 +11,7 @@ type MeasuredValue struct { ID string `json:"id" xml:"id"` Value float64 `json:"value,string" xml:"value,string"` ValueType string `json:"value_type" xml:"value_type"` - FromDate time.Time `json:"from_date" xml:"from_date"` - TillDate time.Time `json:"till_date" xml:"till_date"` + Date time.Time `json:"date" xml:"date"` SensorID string `json:"sensor_id" xml:"sensor_id"` CreationDate *time.Time `json:"creation_date" xml:"creation_date"` UpdateDate *time.Time `json:"update_date" xml:"update_date"` diff --git a/pkg/types/sensor.go b/pkg/types/sensor.go index d926c72..38c13ec 100644 --- a/pkg/types/sensor.go +++ b/pkg/types/sensor.go @@ -7,19 +7,18 @@ import ( // Sensor represents a sensor with all his settings. The struct does not // contains any read method. type Sensor struct { - ID string `json:"id" xml:"id"` - Name string `json:"name" xml:"name"` - Location string `json:"location" xml:"location"` - WireID *string `json:"wire_id" xml:"wire_id"` - I2CBus *int `json:"i2c_bus" xml:"i2c_bus"` - I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"` - GPIONumber string `json:"gpio_number" xml:"gpio_number"` - Model string `json:"model" xml:"model"` - Enabled bool `json:"enabled" xml:"enabled"` - LastContact *time.Time `json:"last_contact" xml:"last_contact"` - TickDuration string `json:"tick_duration" xml:"tick_duration"` - DeviceID string `json:"device_id" xml:"device_id"` - CreationDate time.Time `json:"creation_date" xml:"creation_date"` + ID string `json:"id" xml:"id"` + Name string `json:"name" xml:"name"` + Location string `json:"location" xml:"location"` + WireID *string `json:"wire_id" xml:"wire_id"` + I2CBus *int `json:"i2c_bus" xml:"i2c_bus"` + I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"` + GPIONumber string `json:"gpio_number" xml:"gpio_number"` + Model string `json:"model" xml:"model"` + Enabled bool `json:"enabled" xml:"enabled"` + TickDuration string `json:"tick_duration" xml:"tick_duration"` + DeviceID string `json:"device_id" xml:"device_id"` + CreationDate time.Time `json:"creation_date" xml:"creation_date"` } // GetID returns the UUID of the sensor.