Skip to content

Commit

Permalink
Provide integration tests via the key Ditto features - ContainerFactory
Browse files Browse the repository at this point in the history
[eclipse-kanto#70] Provide integration tests via the key Ditto features - ContainerFactory
- downgrade the paho version in go.mod
- assert for ContinerFactory feature
- assert that all events related with the container creation are received
- add new test method for 'createWithConfig' operation that checks the continaer state
- rename environment variables
- improve the names of the variables

Signed-off-by: Guzgunova Antonia <Antonia.Guzgunova@bosch.io>
  • Loading branch information
antoniyatrifonova committed Nov 15, 2022
1 parent 58c5ce9 commit 6d24b28
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 95 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/containers/ocicrypt v1.1.6
github.com/docker/docker v20.10.17+incompatible
github.com/eclipse/ditto-clients-golang v0.0.0-20220225085802-cf3b306280d3
github.com/eclipse/paho.mqtt.golang v1.3.5
github.com/eclipse/paho.mqtt.golang v1.4.1
github.com/gogo/protobuf v1.3.2
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eclipse/ditto-clients-golang v0.0.0-20220225085802-cf3b306280d3 h1:bfFGs26yNSfhSi6xmnmykB0jZn1Vu5e1/7JA5Wu5aGc=
github.com/eclipse/ditto-clients-golang v0.0.0-20220225085802-cf3b306280d3/go.mod h1:ey7YwfHSQJsinGkGbgeEgqZA7qJnoB0YiFVTFEY50Jg=
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/eclipse/paho.mqtt.golang v1.4.1 h1:tUSpviiL5G3P9SZZJPC4ZULZJsxQKXxfENpMvdbAXAI=
github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
Expand Down
54 changes: 27 additions & 27 deletions integration/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,31 @@ import (

"github.com/eclipse/ditto-clients-golang"
"github.com/eclipse/ditto-clients-golang/model"
mqtt "github.com/eclipse/paho.mqtt.golang"
MQTT "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

type containerManagementSuite struct {
suite.Suite
mqttClient mqtt.Client
dittoClient *ditto.Client
cfg *testConfig
containerID string
containerURL string
containerFactoryURL string
topicModify string
mqttClient MQTT.Client
dittoClient *ditto.Client
cfg *testConfig
ctrThingID string
ctrThingURL string
ctrFactoryFeatureURL string
topicCreated string
topicModify string
topicDeleted string
}

type testConfig struct {
Broker string `def:"tcp://localhost:1883"`
MqttQuiesceMs int `def:"500"`
DittoAddress string
DittoUser string `def:"ditto"`
DittoPassword string `def:"ditto"`
DigitalTwinAPIAddress string
DigitalTwinAPIUser string `def:"ditto"`
DigitalTwinAPIPassword string `def:"ditto"`
EventTimeoutMs int `def:"30000"`
MqttAcknowledgeTimeoutMs int `def:"3000"`
}
Expand All @@ -55,20 +57,16 @@ type requestBody struct {
params map[string]interface{}
}

type jsonFeature struct {
type containerFeature struct {
Definition []string `json:"definition,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
}

type config struct {
DomainName string `json:"domainName,omitempty"`
}

const (
containerFactoryID = "ContainerFactory"
ctrFactoryFeatureID = "ContainerFactory"
)

func (suite *containerManagementSuite) newTestConnection() {
func (suite *containerManagementSuite) connect() {
cfg := &testConfig{}

suite.T().Log(getConfigHelp(*cfg))
Expand All @@ -79,14 +77,14 @@ func (suite *containerManagementSuite) newTestConnection() {

suite.T().Logf("test config: %+v", *cfg)

opts := mqtt.NewClientOptions().
opts := MQTT.NewClientOptions().
AddBroker(cfg.Broker).
SetClientID(uuid.New().String()).
SetKeepAlive(30 * time.Second).
SetCleanSession(true).
SetAutoReconnect(true)

mqttClient := mqtt.NewClient(opts)
mqttClient := MQTT.NewClient(opts)

if token := mqttClient.Connect(); token.Wait() && token.Error() != nil {
require.NoError(suite.T(), token.Error(), "connect to MQTT broker")
Expand All @@ -111,19 +109,21 @@ func (suite *containerManagementSuite) newTestConnection() {
suite.cfg = cfg
suite.dittoClient = dittoClient
suite.mqttClient = mqttClient
suite.containerID = edgeDeviceCfg.DeviceID + ":edge:containers"
suite.containerURL = fmt.Sprintf("%s/api/2/things/%s", strings.TrimSuffix(cfg.DittoAddress, "/"), suite.containerID)
suite.containerFactoryURL = fmt.Sprintf("%s/features/%s", suite.containerURL, containerFactoryID)
ns := model.NewNamespacedIDFrom(suite.containerID)
suite.topicModify = fmt.Sprintf("%s/%s/things/twin/events/modified", ns.Namespace, ns.Name)
suite.ctrThingID = edgeDeviceCfg.DeviceID + ":edge:containers"
suite.ctrThingURL = fmt.Sprintf("%s/api/2/things/%s", strings.TrimSuffix(cfg.DigitalTwinAPIAddress, "/"), suite.ctrThingID)
suite.ctrFactoryFeatureURL = fmt.Sprintf("%s/features/%s", suite.ctrThingURL, ctrFactoryFeatureID)
namespaceID := model.NewNamespacedIDFrom(suite.ctrThingID)
suite.topicCreated = fmt.Sprintf("%s/%s/things/twin/events/created", namespaceID.Namespace, namespaceID.Name)
suite.topicModify = fmt.Sprintf("%s/%s/things/twin/events/modified", namespaceID.Namespace, namespaceID.Name)
suite.topicDeleted = fmt.Sprintf("%s/%s/things/twin/events/deleted", namespaceID.Namespace, namespaceID.Name)
}

func (suite *containerManagementSuite) disconnect() {
suite.dittoClient.Disconnect()
suite.mqttClient.Disconnect(uint(suite.cfg.MqttQuiesceMs))
}

func getContainerID(s string) string {
result := strings.Split(s, "/")
func getCtrFeatureID(topic string) string {
result := strings.Split(topic, "/")
return result[2]
}
130 changes: 88 additions & 42 deletions integration/containerfactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,80 +13,89 @@
package integration

import (
"fmt"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

const (
statusCreated = "CREATED"
statusRunning = "RUNNING"
requestURL = "http://127.0.0.1:5000"
httpResponse = "<html><body><h1>It works!</h1></body></html>\n"
statusCreated = "CREATED"
statusRunning = "RUNNING"
requestURL = "http://127.0.0.1:5000"
httpResponse = "<html><body><h1>It works!</h1></body></html>\n"
ctrFactoryFeatureDefinition = "com.bosch.iot.suite.edge.containers:ContainerFactory:1.2.0"
)

func (suite *containerManagementSuite) SetupSuite() {
suite.newTestConnection()
type ctrFactorySuite struct {
containerManagementSuite
ctrFeatureID string
}

func (suite *containerManagementSuite) TearDownSuite() {
func (suite *ctrFactorySuite) SetupSuite() {
suite.connect()
ctrFactoryFeature := suite.getCtrFeature(ctrFactoryFeatureID)
require.NotNil(suite.T(), ctrFactoryFeature, "ContainerFactory feature must not be nil.")

ctrFactoryFeatureDef := ctrFactoryFeature.GetDefinition()
require.NotNil(suite.T(), ctrFactoryFeatureDef, "ContainerFactory feature definition must not bi nil.")
require.Equal(suite.T(), ctrFactoryFeatureDefinition, ctrFactoryFeatureDef[0].String(), "ContainerFactory feature definition is not equals as expected.")
}

func (suite *ctrFactorySuite) TearDownSuite() {
suite.disconnect()
}

func TestContainerFactorySuite(t *testing.T) {
suite.Run(t, new(containerManagementSuite))
suite.Run(t, new(ctrFactorySuite))
}

func (suite *containerManagementSuite) TestCreateOperation() {
var containerID string
chEvent := suite.startEventListener("START-SEND-EVENTS", func(props map[string]interface{}) bool {
if props["topic"].(string) == suite.topicModify {
containerID = getContainerID(props["path"].(string))
if value, ok := props["value"]; ok {
status, check := value.(map[string]interface{})
return check && status["status"].(string) == statusCreated
}
}
return false
})
func (suite *ctrFactorySuite) TestCreateOperation() {
chEvent := suite.isCreated()

params := make(map[string]interface{})
params["imageRef"] = "docker.io/library/influxdb:1.8.4"
params["start"] = false
params["start"] = true

suite.execCreateCommand("create", params)

require.True(suite.T(), suite.awaitChan(chEvent), "The event not received.")
require.True(suite.T(), suite.awaitChan(chEvent), "The created events are not received.")
require.Equal(suite.T(), statusRunning, suite.getActualCtrState(), "The container state is not expected.")

ctrFeture := suite.getContainerFeture(containerID)
ctrStatusProp := ctrFeture.GetProperty("status").(map[string]interface{})
require.NotNil(suite.T(), ctrStatusProp, "Container feture property 'status' is nil.")
ctrStateProp := ctrStatusProp["state"].(map[string]interface{})
require.Equal(suite.T(), ctrStateProp["status"], statusCreated, "The container state is not expected.")
chEvent = suite.isDeleted()
suite.execRemoveCommand(suite.ctrFeatureID)
require.True(suite.T(), suite.awaitChan(chEvent), "The deleted event not received.")
}

func (suite *ctrFactorySuite) TestCreateWithConfigOperation() {
chEvent := suite.isCreated()

params := make(map[string]interface{})
params["imageRef"] = "docker.io/library/influxdb:1.8.4"
params["start"] = true
params["config"] = make(map[string]interface{})

suite.execRemoveCommand(containerID)
suite.execCreateCommand("createWithConfig", params)

require.True(suite.T(), suite.awaitChan(chEvent), "The event not received.")
require.Equal(suite.T(), statusRunning, suite.getActualCtrState(), "The container state is not expected.")

chEvent = suite.isDeleted()
suite.execRemoveCommand(suite.ctrFeatureID)
require.True(suite.T(), suite.awaitChan(chEvent), "The deleted event not received.")
}

func (suite *containerManagementSuite) TestCreateWithConfigOperation() {
var containerID string
chEvent := suite.startEventListener("START-SEND-EVENTS", func(props map[string]interface{}) bool {
if props["topic"].(string) == suite.topicModify {
containerID = getContainerID(props["path"].(string))
if value, ok := props["value"]; ok {
status, check := value.(map[string]interface{})
return check && status["status"].(string) == statusRunning
}
}
return false
})
func (suite *ctrFactorySuite) TestCreateWithConfigPortMapping() {
chEvent := suite.isCreated()

config := make(map[string]interface{})
config["extraHosts"] = []string{"ctrhost:host_ip"}
config["portMappings"] = []map[string]interface{}{
{
"hostPort": 5000,
"hostPortEnd": 5000,
"containerPort": 80,
},
}
Expand All @@ -103,5 +112,42 @@ func (suite *containerManagementSuite) TestCreateWithConfigOperation() {
data, _ := suite.doRequest(http.MethodGet, requestURL, nil)
require.Equal(suite.T(), httpResponse, string(data), "The HTTP response is not expected.")

suite.execRemoveCommand(containerID)
chEvent = suite.isDeleted()
suite.execRemoveCommand(suite.ctrFeatureID)
require.True(suite.T(), suite.awaitChan(chEvent), "The deleted event not received.")
}

func (suite *ctrFactorySuite) isCreated() chan bool {
return suite.startEventListener("START-SEND-EVENTS", "/features/Container:*", func(props map[string]interface{}) bool {
if props["topic"].(string) == suite.topicCreated {
suite.ctrFeatureID = getCtrFeatureID(props["path"].(string))
return false
}
if props["topic"].(string) == suite.topicModify {
if suite.ctrFeatureID == "" {
return false
}
if value, ok := props["value"]; ok {
status, check := value.(map[string]interface{})
if status["status"].(string) == statusCreated {
return false
}
return check && status["status"].(string) == statusRunning
}
}
return false
})
}

func (suite *ctrFactorySuite) isDeleted() chan bool {
filter := fmt.Sprintf("/features/%s", suite.ctrFeatureID)
return suite.startEventListener("START-SEND-EVENTS", filter, func(props map[string]interface{}) bool {
return props["topic"].(string) == suite.topicDeleted
})
}

func (suite *ctrFactorySuite) getActualCtrState() string {
ctrPropertyPath := fmt.Sprintf("%s/features/%s/properties/status/state/status", suite.ctrThingURL, suite.ctrFeatureID)
data, _ := suite.doRequest(http.MethodGet, ctrPropertyPath, nil)
return strings.Trim(string(data), "\"")
}
10 changes: 6 additions & 4 deletions integration/envcfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ func initConfigFromEnv(cfg interface{}) error {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)

configValue := f.Tag.Get("def")
configValue, ok := f.Tag.Lookup("def")

envName := toSnakeCase(f.Name)
if env, ok := os.LookupEnv(envName); ok {
configValue = env
}

if configValue == "" {
if !ok && configValue == "" {
return fmt.Errorf("env variable %s not set", envName)
}

Expand Down Expand Up @@ -71,8 +71,8 @@ func getConfigHelp(cfg interface{}) string {
result.WriteString("\n\t - ")
result.WriteString(name)

def := f.Tag.Get("def")
if len(def) > 0 {
def, ok := f.Tag.Lookup("def")
if ok {
result.WriteString(fmt.Sprintf(" (default '%s')", def))
}
}
Expand All @@ -81,6 +81,8 @@ func getConfigHelp(cfg interface{}) string {
}

func toSnakeCase(name string) string {
name = strings.ReplaceAll(name, "API", "Api")

var result, word strings.Builder

for i, ch := range name {
Expand Down
Loading

0 comments on commit 6d24b28

Please sign in to comment.