From f365b48d9e05407ecfbdbdeb6e68f5f3332e30fa Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 11:53:53 +0200 Subject: [PATCH 01/22] Add functional tests for container list images and rename Signed-off-by: Fabio Falzoi --- handlers_containers.go | 10 ++- handlers_containers_functional_test.go | 105 +++++++++++++++++++++++++ tests_helper.go | 1 - 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/handlers_containers.go b/handlers_containers.go index 9bb04647..54b2b878 100644 --- a/handlers_containers.go +++ b/handlers_containers.go @@ -129,10 +129,12 @@ func (s *Status) ContainersListImagesEvent(client mqtt.Client, msg mqtt.Message) return } - s.Info("/containers/images", string(data)+"\n") + if !s.SendInfo(s.topicPertinence+"/containers/images", string(data)+"\n") { + fmt.Println("error sending info") + } } -// ContainersListImagesEvent implements docker images +// ContainersRenameEvent implements docker rename func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { cnPayload := ChangeNamePayload{} err := json.Unmarshal(msg.Payload(), &cnPayload) @@ -153,7 +155,9 @@ func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { return } - s.Info("/containers/rename", string(data)+"\n") + if !s.SendInfo(s.topicPertinence+"/containers/rename", string(data)+"\n") { + fmt.Println("error sending info") + } } // ContainersActionEvent implements docker container action like run, start and stop, remove diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index f10b153d..f552733b 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -4,6 +4,8 @@ package main import ( "encoding/json" + "io" + "io/ioutil" "os" "os/exec" "strings" @@ -11,9 +13,11 @@ import ( "time" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" docker "github.com/docker/docker/client" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) // NewMqttTestClientLocal creates mqtt client in localhost:1883 @@ -87,3 +91,104 @@ func TestDockerPsApi(t *testing.T) { assert.True(t, strings.HasPrefix(result[i].ID, containerId)) } } + +func TestDockerListImagesApi(t *testing.T) { + if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { + t.Fatal(token.Error()) + } + + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/images/post", ts.appStatus, ts.appStatus.ContainersListImagesEvent, false) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/images", "{}", 50*time.Millisecond) + + // ask Docker about images effectively present + cmd := exec.Command("bash", "-c", "docker images -a") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + + lines := strings.Split(string(out), "\n") + // Remove the first line (command output header) and the last line (empty line) + lines = lines[1 : len(lines)-1] + + // Take json without INFO tag + resp = strings.TrimPrefix(resp, "INFO: ") + resp = strings.TrimSuffix(resp, "\n\n") + var result []types.ImageSummary + if err := json.Unmarshal([]byte(resp), &result); err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(result), len(lines)) +} + +func TestDockerRenameApi(t *testing.T) { + // create a test container through Docker API + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "docker.io/library/alpine", types.ImagePullOptions{}) + if err != nil { + t.Fatal(err) + } + io.Copy(ioutil.Discard, reader) + + createContResp, err := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := ts.appStatus.dockerClient.ContainerRemove(context.Background(), createContResp.ID, types.ContainerRemoveOptions{}); err != nil { + t.Fatal(err) + } + }() + + if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { + t.Fatal(token.Error()) + } + + cnPayload := ChangeNamePayload{ + ContainerID: createContResp.ID, + ContainerName: "newname", + } + data, err := json.Marshal(cnPayload) + if err != nil { + t.Fatal(err) + } + + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/rename/post", ts.appStatus, ts.appStatus.ContainersRenameEvent, true) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/rename", string(data), 50*time.Millisecond) + + // ask Docker about containers + cmd := exec.Command("bash", "-c", "docker container ls -a") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + + lines := strings.Split(string(out), "\n") + // Remove the first line (command output header) and the last line (empty line) + lines = lines[1 : len(lines)-1] + + // Take json without INFO tag + resp = strings.TrimPrefix(resp, "INFO: ") + resp = strings.TrimSuffix(resp, "\n\n") + var result ChangeNamePayload + if err := json.Unmarshal([]byte(resp), &result); err != nil { + t.Fatal(err) + } + + assert.Equal(t, cnPayload, result) + + // find test container through its ID and check its name + for _, line := range lines { + tokens := strings.Fields(line) + if strings.HasPrefix(result.ContainerID, tokens[0]) { + assert.Equal(t, result.ContainerName, tokens[len(tokens)-1]) + return + } + } + + t.Fatalf("no container with ID %s has been found\n", result.ContainerID) +} diff --git a/tests_helper.go b/tests_helper.go index d3575e7c..41d25c4d 100644 --- a/tests_helper.go +++ b/tests_helper.go @@ -124,5 +124,4 @@ func (tmc *MqttTestClient) MqttSendAndReceiveSync(t *testing.T, topic, request s } wg.Wait() return response - } From 905c653d724c4432387d8502e57b6683462f646f Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 12:04:11 +0200 Subject: [PATCH 02/22] Run all functional tests using the associated build tag --- scripts/functional-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/functional-tests.sh b/scripts/functional-tests.sh index 91611f79..dec81fe5 100755 --- a/scripts/functional-tests.sh +++ b/scripts/functional-tests.sh @@ -5,4 +5,4 @@ set -euo pipefail trap 'kill "$(pidof mosquitto)"' EXIT mosquitto > /dev/null & -go test -v --tags=functional --run="TestDockerPsApi" \ No newline at end of file +go test -v --tags=functional \ No newline at end of file From be9e17ba44d219bcf98ac370a797eb976216cc2b Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 12:49:45 +0200 Subject: [PATCH 03/22] Remove pulled image after the test is done Signed-off-by: Fabio Falzoi --- handlers_containers_functional_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index f552733b..51327843 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" docker "github.com/docker/docker/client" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/stretchr/testify/assert" @@ -123,13 +124,27 @@ func TestDockerListImagesApi(t *testing.T) { } func TestDockerRenameApi(t *testing.T) { - // create a test container through Docker API + // download an alpine image from library to use as test reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "docker.io/library/alpine", types.ImagePullOptions{}) if err != nil { t.Fatal(err) } io.Copy(ioutil.Discard, reader) + defer func() { + reader.Close() + filters := filters.NewArgs(filters.Arg("reference", "alpine")) + images, err := ts.appStatus.dockerClient.ImageList(context.Background(), types.ImageListOptions{Filters: filters}) + if err != nil { + t.Fatal(err) + } + + if _, err := ts.appStatus.dockerClient.ImageRemove(context.Background(), images[0].ID, types.ImageRemoveOptions{}); err != nil { + t.Fatal(err) + } + }() + + // create a test container from downloaded image createContResp, err := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ Image: "alpine", Cmd: []string{"echo", "hello world"}, @@ -137,7 +152,6 @@ func TestDockerRenameApi(t *testing.T) { if err != nil { t.Fatal(err) } - defer func() { if err := ts.appStatus.dockerClient.ContainerRemove(context.Background(), createContResp.ID, types.ContainerRemoveOptions{}); err != nil { t.Fatal(err) From 423b7101f929727cb4e541fcbd01516fa4700b85 Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 22:47:02 +0200 Subject: [PATCH 04/22] Do not connect multiple times to the MQTT broker Signed-off-by: Fabio Falzoi --- handlers_containers_functional_test.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 51327843..e299b2a4 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "io/ioutil" + "log" "os" "os/exec" "strings" @@ -54,16 +55,15 @@ func setupAndRun(m *testing.M) int { ts.appStatus.dockerClient, _ = docker.NewClientWithOpts(docker.WithVersion("1.38")) ts.appStatus.mqttClient = mqtt.NewClient(mqtt.NewClientOptions().AddBroker("tcp://localhost:1883").SetClientID("arduino-connector")) + if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { + log.Fatal(token.Error()) + } defer ts.appStatus.mqttClient.Disconnect(100) return m.Run() } func TestDockerPsApi(t *testing.T) { - if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { - t.Fatal(token.Error()) - } - subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/ps/post", ts.appStatus, ts.appStatus.ContainersPsEvent, false) resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) @@ -94,10 +94,6 @@ func TestDockerPsApi(t *testing.T) { } func TestDockerListImagesApi(t *testing.T) { - if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { - t.Fatal(token.Error()) - } - subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/images/post", ts.appStatus, ts.appStatus.ContainersListImagesEvent, false) resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/images", "{}", 50*time.Millisecond) @@ -158,10 +154,6 @@ func TestDockerRenameApi(t *testing.T) { } }() - if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { - t.Fatal(token.Error()) - } - cnPayload := ChangeNamePayload{ ContainerID: createContResp.ID, ContainerName: "newname", From 80e6ebfcbb1e279a9bc449e7e9eb7f680236326f Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 22:48:14 +0200 Subject: [PATCH 05/22] Increase MQTT timeout time for Docker Rename test Signed-off-by: Fabio Falzoi --- handlers_containers_functional_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index e299b2a4..4ee34861 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -164,7 +164,7 @@ func TestDockerRenameApi(t *testing.T) { } subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/rename/post", ts.appStatus, ts.appStatus.ContainersRenameEvent, true) - resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/rename", string(data), 50*time.Millisecond) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/rename", string(data), 250*time.Millisecond) // ask Docker about containers cmd := exec.Command("bash", "-c", "docker container ls -a") From 5d2f2d781e84148dd519ca6f4eed22249243a0a0 Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 22:49:59 +0200 Subject: [PATCH 06/22] Update functional test script to launch all func tests Signed-off-by: Fabio Falzoi --- scripts/functional-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/functional-tests.sh b/scripts/functional-tests.sh index dec81fe5..08b796ce 100755 --- a/scripts/functional-tests.sh +++ b/scripts/functional-tests.sh @@ -5,4 +5,4 @@ set -euo pipefail trap 'kill "$(pidof mosquitto)"' EXIT mosquitto > /dev/null & -go test -v --tags=functional \ No newline at end of file +go test -v --tags=functional --run="TestDocker" \ No newline at end of file From f5a825cd8497cd41c55980c2e2cc7498511b07f8 Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Mon, 3 Aug 2020 22:50:38 +0200 Subject: [PATCH 07/22] Fix MQTT topic for Container Rename handler Signed-off-by: Fabio Falzoi --- handlers_containers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers_containers.go b/handlers_containers.go index 54b2b878..0aade23b 100644 --- a/handlers_containers.go +++ b/handlers_containers.go @@ -139,7 +139,7 @@ func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { cnPayload := ChangeNamePayload{} err := json.Unmarshal(msg.Payload(), &cnPayload) if err != nil { - s.Error("/containers/action", errors.Wrapf(err, "unmarshal %s", msg.Payload())) + s.Error("/containers/rename", errors.Wrapf(err, "unmarshal %s", msg.Payload())) return } err = s.dockerClient.ContainerRename(context.Background(), cnPayload.ContainerID, cnPayload.ContainerName) From 69e2a857f43757afae8b6064833442960640adb4 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Tue, 4 Aug 2020 15:46:16 +0200 Subject: [PATCH 08/22] Refactor SendInfo to call correctly error func I prefer to remove bool value and call error func directly inside SendInfo, in this way we avoid to check return value of SendInfo. Signed-off-by: Federico Guerinoni --- handlers_containers.go | 12 +++--------- status.go | 8 +++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/handlers_containers.go b/handlers_containers.go index 0aade23b..7ff4695d 100644 --- a/handlers_containers.go +++ b/handlers_containers.go @@ -97,9 +97,7 @@ func (s *Status) ContainersPsEvent(client mqtt.Client, msg mqtt.Message) { return } - if !s.SendInfo(s.topicPertinence+"/containers/ps", string(data)+"\n") { - fmt.Println("error sending info") - } + s.SendInfo(s.topicPertinence+"/containers/ps", string(data)+"\n") } // ContainersListImagesEvent implements docker images @@ -129,9 +127,7 @@ func (s *Status) ContainersListImagesEvent(client mqtt.Client, msg mqtt.Message) return } - if !s.SendInfo(s.topicPertinence+"/containers/images", string(data)+"\n") { - fmt.Println("error sending info") - } + s.SendInfo(s.topicPertinence+"/containers/images", string(data)+"\n") } // ContainersRenameEvent implements docker rename @@ -155,9 +151,7 @@ func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { return } - if !s.SendInfo(s.topicPertinence+"/containers/rename", string(data)+"\n") { - fmt.Println("error sending info") - } + s.SendInfo(s.topicPertinence+"/containers/rename", string(data)+"\n") } // ContainersActionEvent implements docker container action like run, start and stop, remove diff --git a/status.go b/status.go index 8c2f38bc..f241cfea 100644 --- a/status.go +++ b/status.go @@ -125,22 +125,20 @@ func (s *Status) Info(topic, msg string) bool { } // SendInfo send information to a specific topic -func (s *Status) SendInfo(topic, msg string) bool { +func (s *Status) SendInfo(topic, msg string) { if s.mqttClient == nil { - return false + return } s.messagesSent++ if token := s.mqttClient.Publish(topic, 0, false, "INFO: "+msg+"\n"); token.Wait() && token.Error() != nil { - fmt.Println(token.Error()) + s.Error(topic, token.Error()) } if debugMqtt { fmt.Println("MQTT OUT: "+topic, "INFO: "+msg+"\n") } - - return true } // Raw sends a message on the specified topic without further processing From 9f4a0378d423b7e47d836dd85ad942e257441572 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Wed, 5 Aug 2020 11:45:39 +0200 Subject: [PATCH 09/22] WIP: Mock of client mqtt for test error in SendInfo Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 4ee34861..77e4db5a 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -4,6 +4,7 @@ package main import ( "encoding/json" + "fmt" "io" "io/ioutil" "log" @@ -198,3 +199,80 @@ func TestDockerRenameApi(t *testing.T) { t.Fatalf("no container with ID %s has been found\n", result.ContainerID) } + +type MqttClientMock struct{} + +func (m *MqttClientMock) IsConnected() bool { + return false +} + +func (m *MqttClientMock) Connect() mqtt.Token { + return nil +} + +func (m *MqttClientMock) Disconnect(quiesce uint) { +} + +func (m *MqttClientMock) Pubblish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token { + return nil +} + +func (m *MqttClientMock) Subscribe(topic string, qos byte, callback mqtt.MessageHandler) mqtt.Token { + return nil +} + +func (m *MqttClientMock) SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token { + return nil +} + +func (m *MqttClientMock) Unsubscribe(topics ...string) mqtt.Token { + return nil +} + +func (m *MqttClientMock) AddRoute(topic string, callback mqtt.MessageHandler) { + fmt.Println("AddRoute") +} + +func (m *MqttClientMock) OptionsReader() mqtt.ClientOptionsReader { + return mqtt.ClientOptionsReader{} +} + +func TestDockerApiError(t *testing.T) { + // respChan := make(chan string) + // if token := tmc.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) { + // respChan <- string(msg.Payload()) + // }); token.Wait() && token.Error() != nil { + // t.Fatal(token.Error()) + ts.appStatus.mqttClient = MqttClientMock{} + + // ts.appStatus.SendInfo("/container/ps", "error") +} + +// // resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) +// // ts.ui.MqttReceiveWithTimeout(t, "/containers/ps/", 100*time.Millisecond) +// } + +// func (tmc *MqttTestClient) MqttReceiveWithTimeout(t *testing.T, topic string, timeout time.Duration) string { +// t.Helper() + +// respChan := make(chan string) +// if token := tmc.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) { +// respChan <- string(msg.Payload()) +// }); token.Wait() && token.Error() != nil { +// t.Fatal(token.Error()) +// } + +// select { +// case <-time.After(timeout): +// if token := tmc.client.Unsubscribe(topic); token.Wait() && token.Error() != nil { +// t.Fatal(token.Error()) +// } +// close(respChan) + +// t.Fatalf("MqttSendAndReceiveTimeout() timeout for topic %s", topic) + +// return "" +// case resp := <-respChan: +// return resp +// } +// } From 0cc0c12210c1dbdef0a40f127dc3716392117869 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Wed, 5 Aug 2020 17:48:25 +0200 Subject: [PATCH 10/22] Bump github.com/eclipse/paho.mqtt.golang to v1.2.0 This allow us to create a mock test of struct Token. Signed-off-by: Federico Guerinoni --- go.mod | 6 +++--- go.sum | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6bcdccb8..bc085d0d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect github.com/docker/go-units v0.3.3 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/eclipse/paho.mqtt.golang v1.1.1 + github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/facchinm/service v0.0.0-20180209083557-5cffdea7926c github.com/fsnotify/fsnotify v1.4.7 github.com/go-ole/go-ole v1.2.1 // indirect @@ -52,8 +52,8 @@ require ( github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/sirupsen/logrus v1.1.0 // indirect github.com/stretchr/testify v1.3.0 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20190311183353-d8887717615a + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect google.golang.org/grpc v1.29.1 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index c5258de3..81e8a994 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/eclipse/paho.mqtt.golang v1.1.1 h1:iPJYXJLaViCshRTW/PSqImSS6HJ2Rf671WR0bXZ2GIU= -github.com/eclipse/paho.mqtt.golang v1.1.1/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -133,7 +133,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -143,6 +144,9 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -152,6 +156,9 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTu golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= From c8bfc67126181eada744b061abec6b426644e7ee Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 10:19:51 +0200 Subject: [PATCH 11/22] Introduce test to check error sent to broker With this test I mock the mqtt client and token in order to check what application sends in case of an error when publish. Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 99 +++++++++++++------------- status.go | 8 +-- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 77e4db5a..bc59a1fd 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -4,7 +4,6 @@ package main import ( "encoding/json" - "fmt" "io" "io/ioutil" "log" @@ -19,6 +18,7 @@ import ( "github.com/docker/docker/api/types/filters" docker "github.com/docker/docker/client" mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -200,79 +200,78 @@ func TestDockerRenameApi(t *testing.T) { t.Fatalf("no container with ID %s has been found\n", result.ContainerID) } -type MqttClientMock struct{} +type MqttTokenMock struct { + returnErr bool +} -func (m *MqttClientMock) IsConnected() bool { - return false +func (t *MqttTokenMock) Wait() bool { + return true } -func (m *MqttClientMock) Connect() mqtt.Token { +func (t *MqttTokenMock) WaitTimeout(time.Duration) bool { + return true +} + +func (t *MqttTokenMock) Error() error { + if t.returnErr { + return errors.New("test err") + } + return nil } -func (m *MqttClientMock) Disconnect(quiesce uint) { +type MqttClientMock struct { + errPublished string +} + +func (c *MqttClientMock) IsConnected() bool { + return false +} + +func (c *MqttClientMock) IsConnectionOpen() bool { + return true } -func (m *MqttClientMock) Pubblish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token { +func (c *MqttClientMock) Connect() mqtt.Token { return nil } -func (m *MqttClientMock) Subscribe(topic string, qos byte, callback mqtt.MessageHandler) mqtt.Token { +func (c *MqttClientMock) Disconnect(quiesce uint) { +} + +func (c *MqttClientMock) Publish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token { + payloadStr := payload.(string) + if strings.HasPrefix(payloadStr, "INFO") { + return &MqttTokenMock{returnErr: true} + } + + c.errPublished = payloadStr + return &MqttTokenMock{returnErr: false} +} + +func (c *MqttClientMock) Subscribe(topic string, qos byte, callback mqtt.MessageHandler) mqtt.Token { return nil } -func (m *MqttClientMock) SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token { +func (c *MqttClientMock) SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token { return nil } -func (m *MqttClientMock) Unsubscribe(topics ...string) mqtt.Token { +func (c *MqttClientMock) Unsubscribe(topics ...string) mqtt.Token { return nil } -func (m *MqttClientMock) AddRoute(topic string, callback mqtt.MessageHandler) { - fmt.Println("AddRoute") +func (c *MqttClientMock) AddRoute(topic string, callback mqtt.MessageHandler) { } -func (m *MqttClientMock) OptionsReader() mqtt.ClientOptionsReader { +func (c *MqttClientMock) OptionsReader() mqtt.ClientOptionsReader { return mqtt.ClientOptionsReader{} } func TestDockerApiError(t *testing.T) { - // respChan := make(chan string) - // if token := tmc.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) { - // respChan <- string(msg.Payload()) - // }); token.Wait() && token.Error() != nil { - // t.Fatal(token.Error()) - ts.appStatus.mqttClient = MqttClientMock{} - - // ts.appStatus.SendInfo("/container/ps", "error") + ts.appStatus.mqttClient = &MqttClientMock{} + topic := "/container/ps" + ts.appStatus.SendInfo(topic, "error") + mockClient := ts.appStatus.mqttClient.(*MqttClientMock) + assert.Equal(t, mockClient.errPublished, "ERROR: test err\n") } - -// // resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) -// // ts.ui.MqttReceiveWithTimeout(t, "/containers/ps/", 100*time.Millisecond) -// } - -// func (tmc *MqttTestClient) MqttReceiveWithTimeout(t *testing.T, topic string, timeout time.Duration) string { -// t.Helper() - -// respChan := make(chan string) -// if token := tmc.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) { -// respChan <- string(msg.Payload()) -// }); token.Wait() && token.Error() != nil { -// t.Fatal(token.Error()) -// } - -// select { -// case <-time.After(timeout): -// if token := tmc.client.Unsubscribe(topic); token.Wait() && token.Error() != nil { -// t.Fatal(token.Error()) -// } -// close(respChan) - -// t.Fatalf("MqttSendAndReceiveTimeout() timeout for topic %s", topic) - -// return "" -// case resp := <-respChan: -// return resp -// } -// } diff --git a/status.go b/status.go index f241cfea..7211b98c 100644 --- a/status.go +++ b/status.go @@ -103,10 +103,10 @@ func (s *Status) Error(topic string, err error) { return } s.messagesSent++ - token := s.mqttClient.Publish("$aws/things/"+s.id+topic, 1, false, "ERROR: "+err.Error()+"\n") + token := s.mqttClient.Publish(s.topicPertinence+topic, 1, false, "ERROR: "+err.Error()+"\n") token.Wait() if debugMqtt { - fmt.Println("MQTT OUT: $aws/things/"+s.id+topic, "ERROR: "+err.Error()+"\n") + fmt.Println("MQTT OUT: "+s.topicPertinence+s.id+topic, "ERROR: "+err.Error()+"\n") } } @@ -132,12 +132,12 @@ func (s *Status) SendInfo(topic, msg string) { s.messagesSent++ - if token := s.mqttClient.Publish(topic, 0, false, "INFO: "+msg+"\n"); token.Wait() && token.Error() != nil { + if token := s.mqttClient.Publish(s.topicPertinence+topic, 0, false, "INFO: "+msg+"\n"); token.Wait() && token.Error() != nil { s.Error(topic, token.Error()) } if debugMqtt { - fmt.Println("MQTT OUT: "+topic, "INFO: "+msg+"\n") + fmt.Println("MQTT OUT: "+s.topicPertinence+topic, "INFO: "+msg+"\n") } } From 5117cc73004b63b3853d54fb2c51bd9a703284f1 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 11:20:26 +0200 Subject: [PATCH 12/22] Fix linter warnings Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index bc59a1fd..3c0f8a01 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -126,18 +126,22 @@ func TestDockerRenameApi(t *testing.T) { if err != nil { t.Fatal(err) } - io.Copy(ioutil.Discard, reader) + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + defer func() { reader.Close() filters := filters.NewArgs(filters.Arg("reference", "alpine")) - images, err := ts.appStatus.dockerClient.ImageList(context.Background(), types.ImageListOptions{Filters: filters}) - if err != nil { - t.Fatal(err) + images, errImagels := ts.appStatus.dockerClient.ImageList(context.Background(), types.ImageListOptions{Filters: filters}) + if errImagels != nil { + t.Fatal(errImagels) } - if _, err := ts.appStatus.dockerClient.ImageRemove(context.Background(), images[0].ID, types.ImageRemoveOptions{}); err != nil { - t.Fatal(err) + if _, errImageRemove := ts.appStatus.dockerClient.ImageRemove(context.Background(), images[0].ID, types.ImageRemoveOptions{}); errImageRemove != nil { + t.Fatal(errImageRemove) } }() @@ -150,7 +154,7 @@ func TestDockerRenameApi(t *testing.T) { t.Fatal(err) } defer func() { - if err := ts.appStatus.dockerClient.ContainerRemove(context.Background(), createContResp.ID, types.ContainerRemoveOptions{}); err != nil { + if err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), createContResp.ID, types.ContainerRemoveOptions{}); err != nil { t.Fatal(err) } }() From 9bebd2c3568b6898384be7a215044561b85aa2da Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 15:01:29 +0200 Subject: [PATCH 13/22] Add test for API docker action (run) Signed-off-by: Federico Guerinoni --- go.sum | 1 + handlers_containers.go | 3 +- handlers_containers_functional_test.go | 43 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 81e8a994..878634e6 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ github.com/docker/cli v0.0.0-20180905184309-44371c7c34d5/go.mod h1:JLrzqnKDaYBop github.com/docker/cli v17.12.1-ce-rc2+incompatible h1:ESUycEAqvFuLglAHkUW66rCc2djYtd3i1x231svLq9o= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible h1:W1rhCpxfWMU0CaZSFWpmWfmB68zYZVksig+VC1ZbgI4= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v17.12.0-ce-rc1.0.20180822115147-a0385f7ad7f8+incompatible h1:70eRy5NQXxf1cDCjGWCafnNZPMlqI4YRU48QIbAI8tw= github.com/docker/docker v17.12.0-ce-rc1.0.20180822115147-a0385f7ad7f8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g= diff --git a/handlers_containers.go b/handlers_containers.go index 7ff4695d..021b78b0 100644 --- a/handlers_containers.go +++ b/handlers_containers.go @@ -275,8 +275,7 @@ func (s *Status) ContainersActionEvent(client mqtt.Client, msg mqtt.Message) { return } - s.Info("/containers/action", string(data)+"\n") - + s.SendInfo("/containers/action", string(data)+"\n") } // ConfigureRegistryAuth manages registry authentication usage flow diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 3c0f8a01..03f65b30 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -204,6 +204,49 @@ func TestDockerRenameApi(t *testing.T) { t.Fatalf("no container with ID %s has been found\n", result.ContainerID) } +func TestDockerActionRunApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + payload := map[string]interface{}{"action": "run", "image": "alpine", "name": "test-container"} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + + // Check real container runnig with bash command + cmd := exec.Command("bash", "-c", "docker ps") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + + lines := strings.Split(string(out), "\n") + // Remove the first line (command output header) and the last line (empty line) + lines = lines[1 : len(lines)-1] + + assert.Equal(t, len(lines), 1) + assert.True(t, strings.Contains(lines[0], "alpine")) + assert.True(t, strings.Contains(lines[0], "test-container")) + + // Clean up + timeout := 1 * time.Millisecond + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), "test-container", &timeout) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), "test-container", types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } +} + type MqttTokenMock struct { returnErr bool } From a041c00ab7bf07bcb4d0a45277e2185d405e1527 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 15:20:13 +0200 Subject: [PATCH 14/22] Refactor creating func to call cmd on bash Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 54 +++++++------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 03f65b30..9830a153 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -64,20 +64,22 @@ func setupAndRun(m *testing.M) int { return m.Run() } -func TestDockerPsApi(t *testing.T) { - subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/ps/post", ts.appStatus, ts.appStatus.ContainersPsEvent, false) - resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) - - // ask Docker about containers effectively running - cmd := exec.Command("bash", "-c", "docker ps -a") - out, err := cmd.CombinedOutput() +func execCmd(cmd string) []string { + c := exec.Command("bash", "-c", cmd) + out, err := c.CombinedOutput() if err != nil { - t.Fatal(err) + return []string{} } lines := strings.Split(string(out), "\n") - // Remove the first line (command output header) and the last line (empty line) - lines = lines[1 : len(lines)-1] + return lines[1 : len(lines)-1] +} + +func TestDockerPsApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/ps/post", ts.appStatus, ts.appStatus.ContainersPsEvent, false) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) + + lines := execCmd("docker ps -a") // Take json without INFO tag resp = strings.TrimPrefix(resp, "INFO: ") @@ -98,16 +100,7 @@ func TestDockerListImagesApi(t *testing.T) { subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/images/post", ts.appStatus, ts.appStatus.ContainersListImagesEvent, false) resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/images", "{}", 50*time.Millisecond) - // ask Docker about images effectively present - cmd := exec.Command("bash", "-c", "docker images -a") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatal(err) - } - - lines := strings.Split(string(out), "\n") - // Remove the first line (command output header) and the last line (empty line) - lines = lines[1 : len(lines)-1] + lines := execCmd("docker images -a") // Take json without INFO tag resp = strings.TrimPrefix(resp, "INFO: ") @@ -171,16 +164,7 @@ func TestDockerRenameApi(t *testing.T) { subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/rename/post", ts.appStatus, ts.appStatus.ContainersRenameEvent, true) resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/rename", string(data), 250*time.Millisecond) - // ask Docker about containers - cmd := exec.Command("bash", "-c", "docker container ls -a") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatal(err) - } - - lines := strings.Split(string(out), "\n") - // Remove the first line (command output header) and the last line (empty line) - lines = lines[1 : len(lines)-1] + lines := execCmd("docker container ls -a") // Take json without INFO tag resp = strings.TrimPrefix(resp, "INFO: ") @@ -215,15 +199,7 @@ func TestDockerActionRunApi(t *testing.T) { ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) // Check real container runnig with bash command - cmd := exec.Command("bash", "-c", "docker ps") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatal(err) - } - - lines := strings.Split(string(out), "\n") - // Remove the first line (command output header) and the last line (empty line) - lines = lines[1 : len(lines)-1] + lines := execCmd("docker ps") assert.Equal(t, len(lines), 1) assert.True(t, strings.Contains(lines[0], "alpine")) From a11d534b78343845d22a507108ee623711910cef Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 15:36:27 +0200 Subject: [PATCH 15/22] Check if test container is running In this case I prefer this check because we can ignore if other containers are running and is more appropriated then check len of list of containers. Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 9830a153..298e27fa 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -196,14 +196,19 @@ func TestDockerActionRunApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 15*time.Second) // Check real container runnig with bash command lines := execCmd("docker ps") - assert.Equal(t, len(lines), 1) - assert.True(t, strings.Contains(lines[0], "alpine")) - assert.True(t, strings.Contains(lines[0], "test-container")) + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], "test-container") { + foundTestContainerRunning = true + } + } + + assert.True(t, foundTestContainerRunning) // Clean up timeout := 1 * time.Millisecond From 403506885ea59140629e6023f7af9c49731eefad Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 16:03:55 +0200 Subject: [PATCH 16/22] Remane for consistency Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 298e27fa..1fbb656f 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -190,7 +190,8 @@ func TestDockerRenameApi(t *testing.T) { func TestDockerActionRunApi(t *testing.T) { subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) - payload := map[string]interface{}{"action": "run", "image": "alpine", "name": "test-container"} + testContainer := "test-container" + payload := map[string]interface{}{"action": "run", "image": "alpine", "name": testContainer} data, err := json.Marshal(payload) if err != nil { t.Error(err) @@ -203,7 +204,7 @@ func TestDockerActionRunApi(t *testing.T) { foundTestContainerRunning := false for _, l := range lines { - if strings.Contains(l, "alpine") && strings.Contains(lines[0], "test-container") { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { foundTestContainerRunning = true } } @@ -212,12 +213,12 @@ func TestDockerActionRunApi(t *testing.T) { // Clean up timeout := 1 * time.Millisecond - err = ts.appStatus.dockerClient.ContainerStop(context.Background(), "test-container", &timeout) + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) if err != nil { t.Error(err) } - err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), "test-container", types.ContainerRemoveOptions{}) + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) if err != nil { t.Error(err) } From 5657d23863538488bd75d7652d065a1b4deb1221 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 17:56:24 +0200 Subject: [PATCH 17/22] Move clean of test in defer Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 1fbb656f..1c1e5962 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -199,7 +199,6 @@ func TestDockerActionRunApi(t *testing.T) { ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 15*time.Second) - // Check real container runnig with bash command lines := execCmd("docker ps") foundTestContainerRunning := false @@ -211,9 +210,25 @@ func TestDockerActionRunApi(t *testing.T) { assert.True(t, foundTestContainerRunning) - // Clean up - timeout := 1 * time.Millisecond - err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) + defer func() { + timeout := 1 * time.Millisecond + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() +} + if err != nil { t.Error(err) } From 1460989471e7f8c996ab7debdc64f7d2308dcdbc Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 17:58:56 +0200 Subject: [PATCH 18/22] Add test for API docker action (stop) Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 52 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 1c1e5962..a556a7ec 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -229,19 +229,67 @@ func TestDockerActionRunApi(t *testing.T) { }() } +func TestDockerActionStopApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + testContainer := "test-container" + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + err = ts.appStatus.dockerClient.ContainerRename(context.Background(), createContResp.ID, testContainer) if err != nil { t.Error(err) } - err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + err = ts.appStatus.dockerClient.ContainerStart(context.Background(), testContainer, types.ContainerStartOptions{}) if err != nil { t.Error(err) } - _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + payload := map[string]interface{}{"action": "stop", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) if err != nil { t.Error(err) } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + + lines := execCmd("docker ps") + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { + foundTestContainerRunning = true + } + } + + assert.False(t, foundTestContainerRunning) + + defer func() { + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() } type MqttTokenMock struct { From 18b2d232e39edf94ebc9e5d1ef38e570e1c6a508 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 20:30:38 +0200 Subject: [PATCH 19/22] Add test for API docker action (start) Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index a556a7ec..5a8e6e8c 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -292,6 +292,70 @@ func TestDockerActionStopApi(t *testing.T) { }() } +func TestDockerActionStartApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + testContainer := "test-container" + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + err = ts.appStatus.dockerClient.ContainerRename(context.Background(), createContResp.ID, testContainer) + if err != nil { + t.Error(err) + } + + payload := map[string]interface{}{"action": "start", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + + lines := execCmd("docker ps") + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { + foundTestContainerRunning = true + } + } + + assert.True(t, foundTestContainerRunning) + + defer func() { + timeout := 1 * time.Millisecond + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() +} + type MqttTokenMock struct { returnErr bool } From 29cbd488390bce9402f0127bc1a83f9bdc7e90d2 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Thu, 6 Aug 2020 22:08:05 +0200 Subject: [PATCH 20/22] Add test for API docker action (remove) Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 5a8e6e8c..66b6dbdc 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -356,6 +356,46 @@ func TestDockerActionStartApi(t *testing.T) { }() } +func TestDockerActionRemoveApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + payload := map[string]interface{}{"action": "remove", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + + lines := execCmd("docker ps -a") + foundTestContainer := false + for _, l := range lines { + if strings.Contains(l, createContResp.ID) { + foundTestContainer = true + } + } + + assert.False(t, foundTestContainer) +} + type MqttTokenMock struct { returnErr bool } From 3e1f293d37574181d36fd3f50a53000cb26d86f1 Mon Sep 17 00:00:00 2001 From: Federico Guerinoni Date: Fri, 7 Aug 2020 10:00:33 +0200 Subject: [PATCH 21/22] Change timeout for CI Signed-off-by: Federico Guerinoni --- handlers_containers_functional_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 66b6dbdc..21bb85ed 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -197,7 +197,7 @@ func TestDockerActionRunApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 15*time.Second) + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) lines := execCmd("docker ps") @@ -267,7 +267,7 @@ func TestDockerActionStopApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) lines := execCmd("docker ps") foundTestContainerRunning := false @@ -325,7 +325,7 @@ func TestDockerActionStartApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) lines := execCmd("docker ps") foundTestContainerRunning := false @@ -383,7 +383,7 @@ func TestDockerActionRemoveApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 10*time.Second) + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 30*time.Second) lines := execCmd("docker ps -a") foundTestContainer := false From a3fb080c797d140f7b2fe263b08c327966034701 Mon Sep 17 00:00:00 2001 From: Fabio Falzoi Date: Fri, 7 Aug 2020 18:03:45 +0200 Subject: [PATCH 22/22] Use a large timeout in TestDockerActionRemoveApi While executing tests in GitHub Actions environment, a lot of containers are started, so the prune operation can take a very long time. To avoid failing the test due to the high number of containers to prune, we use a very large timeout value. Signed-off-by: Fabio Falzoi --- handlers_containers_functional_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index 21bb85ed..7047e0a5 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -383,7 +383,8 @@ func TestDockerActionRemoveApi(t *testing.T) { t.Error(err) } - ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 30*time.Second) + // Use a very large timeout to avoid failing the test on GitHub Actions environment + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 300*time.Second) lines := execCmd("docker ps -a") foundTestContainer := false