fix: implement repository test
changes: - Implement repository test for the sqlite backend - Add testutils package to start container images - Remove deprecated till_date in measured values - Renamed columns of the table humidities, pressures and temperatures
This commit is contained in:
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
// }
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
);
|
@ -1,2 +1,2 @@
|
||||
DELETE FROM sensors
|
||||
DELETE FROM devices
|
||||
WHERE device_id = $1;
|
@ -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);
|
||||
VALUES ($1, $2, $3, $4, $5, $6);
|
@ -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);
|
||||
VALUES ($1, $2, $3, $4, $5, $6);
|
@ -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);
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
|
@ -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);
|
||||
VALUES ($1, $2, $3, $4, $5, $6);
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
humidity_id,
|
||||
humidity_value,
|
||||
humidity_from_date,
|
||||
humidity_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
humidity_id,
|
||||
humidity_value,
|
||||
humidity_from_date,
|
||||
humidity_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
pressure_id,
|
||||
pressure_value,
|
||||
pressure_from_date,
|
||||
pressure_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
pressure_id,
|
||||
pressure_value,
|
||||
pressure_from_date,
|
||||
pressure_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -8,6 +8,7 @@ SELECT
|
||||
gpio_number,
|
||||
sensor_model,
|
||||
sensor_enabled,
|
||||
tick_duration,
|
||||
device_id,
|
||||
creation_date
|
||||
FROM
|
||||
|
@ -8,7 +8,10 @@ SELECT
|
||||
gpio_number,
|
||||
sensor_model,
|
||||
sensor_enabled,
|
||||
tick_duration,
|
||||
device_id,
|
||||
creation_date
|
||||
FROM
|
||||
sensors;
|
||||
sensors
|
||||
ORDER BY
|
||||
sensor_id;
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
temperature_id,
|
||||
temperature_value,
|
||||
temperature_from_date,
|
||||
temperature_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -1,8 +1,7 @@
|
||||
SELECT
|
||||
temperature_id,
|
||||
temperature_value,
|
||||
temperature_from_date,
|
||||
temperature_till_date,
|
||||
id,
|
||||
value,
|
||||
date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
|
@ -1,7 +1,7 @@
|
||||
UPDATE device
|
||||
UPDATE devices
|
||||
SET
|
||||
device_name = $1,
|
||||
device_location = $2,
|
||||
creation_date = $3
|
||||
WHERE
|
||||
device_id = $4,
|
||||
device_id = $4
|
@ -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;
|
||||
sensor_id = $12;
|
@ -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
|
||||
|
309
pkg/repository/repository_test.go
Normal file
309
pkg/repository/repository_test.go
Normal file
@ -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
|
||||
}
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
200
pkg/testutils/dockerutils/builder.go
Normal file
200
pkg/testutils/dockerutils/builder.go
Normal file
@ -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
|
||||
}
|
220
pkg/testutils/dockerutils/client.go
Normal file
220
pkg/testutils/dockerutils/client.go
Normal file
@ -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
|
||||
}
|
164
pkg/testutils/dockerutils/watcher.go
Normal file
164
pkg/testutils/dockerutils/watcher.go
Normal file
@ -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
|
||||
}
|
@ -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"`
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user