From 33566bed16e3df61ae7cda5d35a9696f81fa3221 Mon Sep 17 00:00:00 2001 From: Nicholas Daffern Date: Thu, 30 Mar 2023 09:44:39 +0100 Subject: [PATCH] Use Podman and Docker CLI for Container tests instead of Docker API (#415) Signed-off-by: Nicholas-Daffern --- Makefile | 44 +- .../containerengine/containerengine.go | 669 ++++++++++++++++++ test/{docker => container}/devconfig_test.go | 398 +++++------ .../devconfig_test_util.go | 57 +- test/{docker => container}/docker_api_test.go | 492 +++++-------- .../docker_api_test_util.go | 440 ++++-------- test/container/go.mod | 3 + test/container/go.sum | 0 test/{docker => container}/main.go | 0 .../mq_multi_instance_test.go | 59 +- .../mq_multi_instance_test_util.go | 22 +- .../mq_native_ha_test.go | 163 ++--- .../mq_native_ha_test_util.go | 64 +- test/{docker => container}/mqmetric_test.go | 101 +-- .../mqmetric_test_util.go | 8 +- test/docker/go.mod | 40 -- test/docker/go.sum | 199 ------ .../com/ibm/mqcontainer/test/JMSTests.java | 27 +- 18 files changed, 1466 insertions(+), 1320 deletions(-) create mode 100644 test/container/containerengine/containerengine.go rename test/{docker => container}/devconfig_test.go (66%) rename test/{docker => container}/devconfig_test_util.go (85%) rename test/{docker => container}/docker_api_test.go (83%) rename test/{docker => container}/docker_api_test_util.go (58%) create mode 100644 test/container/go.mod create mode 100644 test/container/go.sum rename test/{docker => container}/main.go (100%) rename test/{docker => container}/mq_multi_instance_test.go (83%) rename test/{docker => container}/mq_multi_instance_test_util.go (72%) rename test/{docker => container}/mq_native_ha_test.go (67%) rename test/{docker => container}/mq_native_ha_test_util.go (65%) rename test/{docker => container}/mqmetric_test.go (90%) rename test/{docker => container}/mqmetric_test_util.go (97%) delete mode 100644 test/docker/go.mod delete mode 100644 test/docker/go.sum diff --git a/Makefile b/Makefile index d8b9502..847802c 100644 --- a/Makefile +++ b/Makefile @@ -45,10 +45,10 @@ MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION_VRM)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH)_NOI MQ_ARCHIVE_DEV ?= $(MQ_VERSION)-IBM-MQ-Advanced-for-Developers-Non-Install-$(MQ_ARCHIVE_DEV_TYPE)$(MQ_ARCHIVE_DEV_ARCH).tar.gz # MQ_SDK_ARCHIVE specifies the archive to use for building the golang programs. Defaults vary on developer or advanced. MQ_SDK_ARCHIVE ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION)) -# Options to `go test` for the Docker tests -TEST_OPTS_DOCKER ?= -# Timeout for the Docker tests -TEST_TIMEOUT_DOCKER ?= 45m +# Options to `go test` for the Container tests +TEST_OPTS_CONTAINER ?= +# Timeout for the tests +TEST_TIMEOUT_CONTAINER ?= 45m # MQ_IMAGE_ADVANCEDSERVER is the name of the built MQ Advanced image MQ_IMAGE_ADVANCEDSERVER ?=ibm-mqadvanced-server # MQ_IMAGE_DEVSERVER is the name of the built MQ Advanced for Developers image @@ -278,9 +278,9 @@ cache-mq-tag: # Test targets ############################################################################### -# Vendor Go dependencies for the Docker tests -test/docker/vendor: - cd test/docker && go mod vendor +# Vendor Go dependencies for the Container tests +test/container/vendor: + cd test/container && go mod vendor # Shortcut to just run the unit tests .PHONY: test-unit @@ -288,28 +288,28 @@ test-unit: $(COMMAND) build --target builder --file Dockerfile-server . .PHONY: test-advancedserver -test-advancedserver: test/docker/vendor +test-advancedserver: test/container/vendor $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) on $(shell $(COMMAND) --version)"$(END))) $(COMMAND) inspect $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) - cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) EXPECTED_LICENSE=Production DOCKER_API_VERSION=$(DOCKER_API_VERSION) go test -parallel $(NUM_CPU) -timeout $(TEST_TIMEOUT_DOCKER) $(TEST_OPTS_DOCKER) + cd test/container && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) EXPECTED_LICENSE=Production DOCKER_API_VERSION=$(DOCKER_API_VERSION) COMMAND=$(COMMAND) go test -parallel $(NUM_CPU) -timeout $(TEST_TIMEOUT_CONTAINER) $(TEST_OPTS_CONTAINER) .PHONY: build-devjmstest build-devjmstest: $(info $(SPACER)$(shell printf $(TITLE)"Build JMS tests for developer config"$(END))) - cd test/messaging && docker build --tag $(DEV_JMS_IMAGE) . + cd test/messaging && $(COMMAND) build --tag $(DEV_JMS_IMAGE) . .PHONY: test-devserver -test-devserver: test/docker/vendor +test-devserver: test/container/vendor $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) on $(shell $(COMMAND) --version)"$(END))) $(COMMAND) inspect $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) - cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER):$(MQ_TAG) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=false DOCKER_API_VERSION=$(DOCKER_API_VERSION) go test -parallel $(NUM_CPU) -timeout $(TEST_TIMEOUT_DOCKER) -tags mqdev $(TEST_OPTS_DOCKER) + cd test/container && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER):$(MQ_TAG) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=false DOCKER_API_VERSION=$(DOCKER_API_VERSION) COMMAND=$(COMMAND) go test -parallel $(NUM_CPU) -timeout $(TEST_TIMEOUT_CONTAINER) -tags mqdev $(TEST_OPTS_CONTAINER) .PHONY: coverage coverage: mkdir coverage .PHONY: test-advancedserver-cover -test-advancedserver-cover: test/docker/vendor coverage +test-advancedserver-cover: test/container/vendor coverage $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) with code coverage on $(shell $(COMMAND) --version)"$(END))) rm -f ./coverage/unit*.cov # Run unit tests with coverage, for each package under 'internal' @@ -319,16 +319,16 @@ test-advancedserver-cover: test/docker/vendor coverage tail -q -n +2 ./coverage/unit-*.cov >> ./coverage/unit.cov go tool cover -html=./coverage/unit.cov -o ./coverage/unit.html - rm -f ./test/docker/coverage/*.cov - rm -f ./coverage/docker.* - mkdir -p ./test/docker/coverage/ - cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover TEST_COVER=true DOCKER_API_VERSION=$(DOCKER_API_VERSION) go test $(TEST_OPTS_DOCKER) - echo 'mode: count' > ./coverage/docker.cov - tail -q -n +2 ./test/docker/coverage/*.cov >> ./coverage/docker.cov - go tool cover -html=./coverage/docker.cov -o ./coverage/docker.html + rm -f ./test/container/coverage/*.cov + rm -f ./coverage/container.* + mkdir -p ./test/container/coverage/ + cd test/container && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover TEST_COVER=true DOCKER_API_VERSION=$(DOCKER_API_VERSION) go test $(TEST_OPTS_CONTAINER) + echo 'mode: count' > ./coverage/container.cov + tail -q -n +2 ./test/container/coverage/*.cov >> ./coverage/container.cov + go tool cover -html=./coverage/container.cov -o ./coverage/container.html echo 'mode: count' > ./coverage/combined.cov - tail -q -n +2 ./coverage/unit.cov ./coverage/docker.cov >> ./coverage/combined.cov + tail -q -n +2 ./coverage/unit.cov ./coverage/container.cov >> ./coverage/combined.cov go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html ############################################################################### @@ -569,4 +569,4 @@ ifneq (,$(findstring docker,$(COMMAND))) endif ifneq (,$(findstring podman,$(COMMAND))) @test "$(word 1,$(subst ., ,$(PODMAN_VERSION)))" -ge "1" || (echo "Error: Podman version 1.0 or greater is required" && exit 1) -endif +endif \ No newline at end of file diff --git a/test/container/containerengine/containerengine.go b/test/container/containerengine/containerengine.go new file mode 100644 index 0000000..c9b475d --- /dev/null +++ b/test/container/containerengine/containerengine.go @@ -0,0 +1,669 @@ +/* +© Copyright IBM Corporation 2017, 2023 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package containerengine + +import ( + "context" + "encoding/json" + "io" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" +) + +type ContainerInterface interface { + ContainerCreate(config *ContainerConfig, hostConfig *ContainerHostConfig, networkingConfig *ContainerNetworkSettings, containerName string) (string, error) + ContainerStop(container string, timeout *time.Duration) error + ContainerKill(container string, signal string) error + ContainerRemove(container string, options ContainerRemoveOptions) error + ContainerStart(container string, options ContainerStartOptions) error + ContainerWait(ctx context.Context, container string, condition string) (<-chan int64, <-chan error) + GetContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (string, error) + CopyFromContainer(container, srcPath string) ([]byte, error) + + GetContainerPort(ID string, hostPort int) (string, error) + GetContainerIPAddress(ID string) (string, error) + ContainerInspectWithFormat(format string, ID string) (string, error) + ExecContainer(ID string, user string, cmd []string) (int, string) + GetMQVersion(image string) (string, error) + ContainerInspect(containerID string) (ContainerDetails, error) + + NetworkCreate(name string, options NetworkCreateOptions) (string, error) + NetworkRemove(network string) error + + VolumeCreate(options VolumeCreateOptions) (string, error) + VolumeRemove(volumeID string, force bool) error + + ImageBuild(context io.Reader, tag string, dockerfilename string) (string, error) + ImageRemove(image string, options ImageRemoveOptions) (bool, error) + ImageInspectWithFormat(format string, ID string) (string, error) +} + +type ContainerClient struct { + ContainerTool string + Version string +} + +// objects +var objVolume = "volume" +var objImage = "image" +var objPort = "port" +var objNetwork = "network" + +// verbs +var listContainers = "ps" +var listImages = "images" +var create = "create" +var startContainer = "start" +var waitContainer = "wait" +var execContainer = "exec" +var getLogs = "logs" +var stopContainer = "stop" +var remove = "rm" +var inspect = "inspect" +var copyFile = "cp" +var build = "build" +var killContainer = "kill" + +// args +var argEntrypoint = "--entrypoint" +var argUser = "--user" +var argExpose = "--expose" +var argVolume = "--volume" +var argPublish = "--publish" +var argPrivileged = "--privileged" +var argAddCapability = "--cap-add" +var argDropCapability = "--cap-drop" +var argName = "--name" +var argCondition = "--condition" +var argEnvironmentVariable = "--env" +var argTail = "--tail" +var argForce = "--force" +var argVolumes = "--volumes" +var argHostname = "--hostname" +var argDriver = "--driver" +var argFile = "--file" +var argQuiet = "--quiet" +var argTag = "--tag" +var argFormat = "--format" +var argNetwork = "--network" +var argSecurityOptions = "--security-opt" +var argSignal = "--signal" + +// generic +var toolVersion = "version" +var ContainerStateNotRunning = "not-running" +var ContainerStateStopped = "stopped" + +type ContainerConfig struct { + Image string + Hostname string + User string + Entrypoint []string + Env []string + ExposedPorts []string +} + +type ContainerDetails struct { + ID string + Name string + Image string + Path string + Args []string + Config ContainerConfig + HostConfig ContainerHostConfig +} + +type ContainerDetailsLogging struct { + ID string + Name string + Image string + Path string + Args []string + CapAdd []string + CapDrop []string + User string + Env []string +} + +type ContainerHostConfig struct { + Binds []string // Bindings onto a volume + PortBindings []PortBinding //Bindings from a container port to a port on the host + Privileged bool // Give extended privileges to container + CapAdd []string // Linux capabilities to add to the container + CapDrop []string // Linux capabilities to drop from the container + SecurityOpt []string +} + +type ContainerNetworkSettings struct { + Networks []string // A list of networks to connect the container to +} + +type ContainerRemoveOptions struct { + Force bool + RemoveVolumes bool +} + +type ContainerStartOptions struct { +} + +type NetworkCreateOptions struct { +} + +type ContainerLogsOptions struct { +} + +type ImageRemoveOptions struct { + Force bool +} + +type VolumeCreateOptions struct { + Name string + Driver string +} + +// Binding from a container port to a port on the host +type PortBinding struct { + HostIP string + HostPort string //Port to map to on the host + ContainerPort string //Exposed port on the container +} + +// NewContainerClient returns a new container client +// Defaults to using podman +func NewContainerClient() ContainerClient { + tool, set := os.LookupEnv("COMMAND") + if !set { + tool = "podman" + } + return ContainerClient{ + ContainerTool: tool, + Version: GetContainerToolVersion(tool), + } +} + +// GetContainerToolVersion returns the version of the container tool being used +func GetContainerToolVersion(containerTool string) string { + if containerTool == "docker" { + args := []string{"version", "--format", "'{{.Client.Version}}'"} + v, err := exec.Command("docker", args...).Output() + if err != nil { + return "0.0.0" + } + return string(v) + } else if containerTool == "podman" { + //Default to checking the version of podman + args := []string{"version", "--format", "'{{.Version}}'"} + v, err := exec.Command("podman", args...).Output() + if err != nil { + return "0.0.0" + } + return string(v) + } + return "0.0.0" +} + +// GetMQVersion returns the MQ version of a given container image +func (cli ContainerClient) GetMQVersion(image string) (string, error) { + v, err := cli.ImageInspectWithFormat("{{.Config.Labels.version}}", image) + if err != nil { + return "", err + } + return v, nil +} + +// ImageInspectWithFormat inspects an image with a given formatting string +func (cli ContainerClient) ImageInspectWithFormat(format string, ID string) (string, error) { + args := []string{ + objImage, + inspect, + ID, + } + if format != "" { + args = append(args, []string{argFormat, format}...) + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return "", err + } + return string(output), nil +} + +// ContainerInspectWithFormat inspects a container with a given formatting string +func (cli ContainerClient) ContainerInspectWithFormat(format string, ID string) (string, error) { + args := []string{ + inspect, + ID, + } + if format != "" { + args = append(args, []string{argFormat, format}...) + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return "", err + } + return string(output), nil +} + +// GetContainerPort gets the ports on a container +func (cli ContainerClient) GetContainerPort(ID string, hostPort int) (string, error) { + args := []string{ + objPort, + ID, + strconv.Itoa(hostPort), + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return "", err + } + o := SanitizeString(string(output)) + return strings.Split((o), ":")[1], nil +} + +// GetContainerIPAddress gets the IP address of a container +func (cli ContainerClient) GetContainerIPAddress(ID string) (string, error) { + v, err := cli.ContainerInspectWithFormat("{{.NetworkSettings.IPAddress}}", ID) + if err != nil { + return "", err + } + return v, nil +} + +// CopyFromContainer copies a file from a container and returns its contents +func (cli ContainerClient) CopyFromContainer(container, srcPath string) ([]byte, error) { + tmpDir, err := os.MkdirTemp("", "tmp") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpDir) + args := []string{ + copyFile, + container + ":" + srcPath, + tmpDir + "/.", + } + _, err = exec.Command(cli.ContainerTool, args...).CombinedOutput() + if err != nil { + return nil, err + } + //Get file name + fname := filepath.Base(srcPath) + data, err := os.ReadFile(filepath.Join(tmpDir, fname)) + if err != nil { + return nil, err + } + + //Remove the file + err = os.Remove(filepath.Join(tmpDir, fname)) + if err != nil { + return nil, err + } + return data, nil +} + +func (cli ContainerClient) ContainerInspect(containerID string) (ContainerDetails, error) { + args := []string{ + inspect, + containerID, + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return ContainerDetails{}, err + } + + var container ContainerDetails + err = json.Unmarshal(output, &container) + if err != nil { + return ContainerDetails{}, err + } + return container, err +} + +func (cli ContainerClient) ContainerStop(container string, timeout *time.Duration) error { + args := []string{ + stopContainer, + container, + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + return err +} + +func (cli ContainerClient) ContainerKill(container string, signal string) error { + args := []string{ + killContainer, + container, + } + if signal != "" { + args = append(args, []string{argSignal, signal}...) + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + return err +} + +func (cli ContainerClient) ContainerRemove(container string, options ContainerRemoveOptions) error { + args := []string{ + remove, + container, + } + if options.Force { + args = append(args, argForce) + } + if options.RemoveVolumes { + args = append(args, argVolumes) + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + //Silently error as the exit code 125 is present on sucessful deletion + if strings.Contains(err.Error(), "125") { + return nil + } + return err + } + return nil +} + +func (cli ContainerClient) ExecContainer(ID string, user string, cmd []string) (int, string) { + args := []string{ + execContainer, + } + if user != "" { + args = append(args, []string{argUser, user}...) + } + args = append(args, ID) + args = append(args, cmd...) + ctx := context.Background() + output, err := exec.CommandContext(ctx, cli.ContainerTool, args...).CombinedOutput() + if err != nil { + if err.(*exec.ExitError) != nil { + return err.(*exec.ExitError).ExitCode(), string(output) + } else { + return 9897, string(output) + } + } + return 0, string(output) +} + +func (cli ContainerClient) ContainerStart(container string, options ContainerStartOptions) error { + args := []string{ + startContainer, + container, + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + return err +} + +// ContainerWait starts waiting for a container. It returns an int64 channel for receiving an exit code and an error channel for receiving errors. +// The channels returned from this function should be used to receive the results from the wait command. +func (cli ContainerClient) ContainerWait(ctx context.Context, container string, condition string) (<-chan int64, <-chan error) { + args := []string{ + waitContainer, + container, + } + if cli.ContainerTool == "podman" { + if condition == ContainerStateNotRunning { + condition = ContainerStateStopped + } + args = append(args, []string{argCondition, string(condition)}...) + } + + resultC := make(chan int64) + errC := make(chan error, 1) + + output, err := exec.CommandContext(ctx, cli.ContainerTool, args...).Output() + if err != nil { + errC <- err + return resultC, errC + } + + go func() { + out := strings.TrimSuffix(string(output), "\n") + exitCode, err := strconv.Atoi(out) + if err != nil { + errC <- err + return + } + resultC <- int64(exitCode) + }() + + return resultC, errC +} + +func (cli ContainerClient) GetContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (string, error) { + args := []string{ + getLogs, + container, + } + output, err := exec.Command(cli.ContainerTool, args...).CombinedOutput() + if err != nil { + return "", err + } + return string(output), nil +} + +func (cli ContainerClient) NetworkCreate(name string, options NetworkCreateOptions) (string, error) { + args := []string{ + objNetwork, + create, + } + netID, err := exec.Command(cli.ContainerTool, args...).CombinedOutput() + if err != nil { + return "", err + } + networkID := SanitizeString(string(netID)) + + return networkID, nil +} + +func (cli ContainerClient) NetworkRemove(network string) error { + args := []string{ + objNetwork, + remove, + } + _, err := exec.Command(cli.ContainerTool, args...).CombinedOutput() + return err +} + +func (cli ContainerClient) VolumeCreate(options VolumeCreateOptions) (string, error) { + args := []string{ + objVolume, + create, + options.Name, + } + if options.Driver != "" { + args = append(args, []string{argDriver, options.Driver}...) + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return "", err + } + name := SanitizeString(string(output)) + return name, nil +} + +func (cli ContainerClient) VolumeRemove(volumeID string, force bool) error { + args := []string{ + objVolume, + remove, + volumeID, + } + if force { + args = append(args, argForce) + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + return err +} + +func (cli ContainerClient) ImageBuild(context io.Reader, tag string, dockerfilename string) (string, error) { + args := []string{ + objImage, + build, + } + //dockerfilename includes the path to the dockerfile + //When using podman use the full path including the name of the Dockerfile + if cli.ContainerTool == "podman" { + args = append(args, []string{argFile, dockerfilename}...) + } + if tag != "" { + args = append(args, []string{argTag, tag}...) + } + args = append(args, argQuiet) + //When using docker remove the name 'DockerFile' from the string + if cli.ContainerTool == "docker" { + dfn := strings.ReplaceAll(dockerfilename, "Dockerfile", "") + args = append(args, dfn) + } + output, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return "", err + } + sha := SanitizeString(string(output)) + return sha, nil +} + +func (cli ContainerClient) ImageRemove(image string, options ImageRemoveOptions) (bool, error) { + args := []string{ + objImage, + remove, + image, + } + if options.Force { + args = append(args, argForce) + } + _, err := exec.Command(cli.ContainerTool, args...).Output() + if err != nil { + return false, err + } + return true, nil +} + +func (cli ContainerClient) ContainerCreate(config *ContainerConfig, hostConfig *ContainerHostConfig, networkingConfig *ContainerNetworkSettings, containerName string) (string, error) { + args := []string{ + create, + argName, + containerName, + } + args = getHostConfigArgs(args, hostConfig) + args = getNetworkConfigArgs(args, networkingConfig) + args = getContainerConfigArgs(args, config, cli.ContainerTool) + output, err := exec.Command(cli.ContainerTool, args...).Output() + lines := strings.Split(strings.ReplaceAll(string(output), "\r\n", "\n"), "\n") + if err != nil { + return lines[0], err + } + return lines[0], nil +} + +// getContainerConfigArgs converts a ContainerConfig into a set of cli arguments +func getContainerConfigArgs(args []string, config *ContainerConfig, toolName string) []string { + argList := []string{} + if config.Entrypoint != nil && toolName == "podman" { + entrypoint := "[\"" + for i, commandPart := range config.Entrypoint { + if i != len(config.Entrypoint)-1 { + entrypoint += commandPart + "\",\"" + } else { + //terminate list + entrypoint += commandPart + "\"]" + } + } + args = append(args, []string{argEntrypoint, entrypoint}...) + } + if config.Entrypoint != nil && toolName == "docker" { + ep1 := "" + for i, commandPart := range config.Entrypoint { + if i == 0 { + ep1 = commandPart + } else { + argList = append(argList, commandPart) + } + } + args = append(args, []string{argEntrypoint, ep1}...) + } + if config.User != "" { + args = append(args, []string{argUser, config.User}...) + } + if config.ExposedPorts != nil { + for _, port := range config.ExposedPorts { + args = append(args, []string{argExpose, port}...) + } + } + if config.Hostname != "" { + args = append(args, []string{argHostname, config.Hostname}...) + } + for _, env := range config.Env { + args = append(args, []string{argEnvironmentVariable, env}...) + } + if config.Image != "" { + args = append(args, config.Image) + } + if config.Entrypoint != nil && toolName == "docker" { + args = append(args, argList...) + } + return args +} + +// getHostConfigArgs converts a ContainerHostConfig into a set of cli arguments +func getHostConfigArgs(args []string, hostConfig *ContainerHostConfig) []string { + if hostConfig.Binds != nil { + for _, volume := range hostConfig.Binds { + args = append(args, []string{argVolume, volume}...) + } + } + if hostConfig.PortBindings != nil { + for _, binding := range hostConfig.PortBindings { + pub := binding.HostIP + ":" + binding.HostPort + ":" + binding.ContainerPort + args = append(args, []string{argPublish, pub}...) + } + } + if hostConfig.Privileged { + args = append(args, []string{argPrivileged}...) + } + if hostConfig.CapAdd != nil { + for _, capability := range hostConfig.CapAdd { + args = append(args, []string{argAddCapability, string(capability)}...) + } + } + if hostConfig.CapDrop != nil { + for _, capability := range hostConfig.CapDrop { + args = append(args, []string{argDropCapability, string(capability)}...) + } + } + if hostConfig.SecurityOpt != nil { + for _, securityOption := range hostConfig.SecurityOpt { + args = append(args, []string{argSecurityOptions, string(securityOption)}...) + } + } + return args +} + +// getNetworkConfigArgs converts a set of ContainerNetworkSettings into a set of cli arguments +func getNetworkConfigArgs(args []string, networkingConfig *ContainerNetworkSettings) []string { + if networkingConfig.Networks != nil { + for _, netID := range networkingConfig.Networks { + args = append(args, []string{argNetwork, netID}...) + } + } + return args +} + +func SanitizeString(s string) string { + s = strings.Replace(s, " ", "", -1) + s = strings.Replace(s, "\t", "", -1) + s = strings.Replace(s, "\n", "", -1) + return s +} diff --git a/test/docker/devconfig_test.go b/test/container/devconfig_test.go similarity index 66% rename from test/docker/devconfig_test.go rename to test/container/devconfig_test.go index e057d8a..bb8e74c 100644 --- a/test/docker/devconfig_test.go +++ b/test/container/devconfig_test.go @@ -2,7 +2,7 @@ // +build mqdev /* -© Copyright IBM Corporation 2018, 2022 +© Copyright IBM Corporation 2018, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,37 +19,30 @@ limitations under the License. package main import ( - "context" + "crypto/tls" + "fmt" "path/filepath" "strings" "testing" "time" - "crypto/tls" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) // TestDevGoldenPath tests using the default values for the default developer config. // Note: This test requires a separate container image to be available for the JMS tests. func TestDevGoldenPath(t *testing.T) { t.Parallel() - - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() qm := "qm1" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + qm, "DEBUG=true", }, } - id := runContainerWithPorts(t, cli, &containerConfig, []int{9443}) + id := runContainerWithPorts(t, cli, &containerConfig, []int{9443, 1414}) defer cleanContainer(t, cli, id) waitForReady(t, cli, id) waitForWebReady(t, cli, id, insecureTLSConfig) @@ -74,15 +67,12 @@ func TestDevGoldenPath(t *testing.T) { func TestDevSecure(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() const tlsPassPhrase string = "passw0rd" qm := "qm1" appPassword := "differentPassw0rd" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + qm, @@ -93,67 +83,67 @@ func TestDevSecure(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, - // Assign a random port for the web server on the host - // TODO: Don't do this for all tests - PortBindings: nat.PortMap{ - "9443/tcp": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - }, - }, - }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + // Assign a random port for the web server on the host + // TODO: Don't do this for all tests + var binding ce.PortBinding + ports := []int{9443, 1414} + for _, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) cert := filepath.Join(tlsDir(t, true), "server.crt") - waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase)) + waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase)) t.Run("JMS", func(t *testing.T) { // OpenJDK is used for running tests, hence pass "false" for 7th parameter. // Cipher name specified is compliant with non-IBM JRE naming. - runJMSTests(t, cli, ctr.ID, true, "app", appPassword, "false", "TLS_RSA_WITH_AES_256_CBC_SHA256") + runJMSTests(t, cli, ID, true, "app", appPassword, "false", "TLS_RSA_WITH_AES_256_CBC_SHA256") }) t.Run("REST admin", func(t *testing.T) { - testRESTAdmin(t, cli, ctr.ID, insecureTLSConfig, "") + testRESTAdmin(t, cli, ID, insecureTLSConfig, "") }) t.Run("REST messaging", func(t *testing.T) { - testRESTMessaging(t, cli, ctr.ID, insecureTLSConfig, qm, "app", appPassword, "") + testRESTMessaging(t, cli, ID, insecureTLSConfig, qm, "app", appPassword, "") }) // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } func TestDevWebDisabled(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", "MQ_ENABLE_EMBEDDED_WEB_SERVER=false", }, } - id := runContainer(t, cli, &containerConfig) + id := runContainerWithPorts(t, cli, &containerConfig, []int{1414}) defer cleanContainer(t, cli, id) waitForReady(t, cli, id) t.Run("Web", func(t *testing.T) { - _, dspmqweb := execContainer(t, cli, id, "", []string{"dspmqweb"}) + _, dspmqweb := cli.ExecContainer(id, "", []string{"dspmqweb"}) if !strings.Contains(dspmqweb, "Server mqweb is not running.") && !strings.Contains(dspmqweb, "MQWB1125I") { t.Errorf("Expected dspmqweb to say 'Server is not running' or 'MQWB1125I'; got \"%v\"", dspmqweb) } @@ -171,11 +161,8 @@ func TestDevWebDisabled(t *testing.T) { func TestDevConfigDisabled(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -199,11 +186,8 @@ func TestDevConfigDisabled(t *testing.T) { func TestSSLKEYRBlank(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -246,12 +230,9 @@ func TestSSLKEYRBlank(t *testing.T) { func TestSSLKEYRWithSuppliedKeyAndCert(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -259,24 +240,24 @@ func TestSSLKEYRWithSuppliedKeyAndCert(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // execute runmqsc to display qmgr SSLKEYR and CERTLABL attibutes. // Search the console output for exepcted values - _, sslkeyROutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) + _, sslkeyROutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) if !strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") || !strings.Contains(sslkeyROutput, "CERTLABL(default)") { // Although queue manager is ready, it may be that MQSC scripts have not been applied yet. // Hence wait for a second and retry few times before giving up. @@ -284,33 +265,30 @@ func TestSSLKEYRWithSuppliedKeyAndCert(t *testing.T) { var i int for i = 0; i < waitCount; i++ { time.Sleep(1 * time.Second) - _, sslkeyROutput = execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) + _, sslkeyROutput = execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) if strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") && strings.Contains(sslkeyROutput, "CERTLABL(default)") { break } } // Failed to get expected output? dump the contents of mqsc files. if i == waitCount { - _, tls15mqsc := execContainer(t, cli, ctr.ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) - _, autoMQSC := execContainer(t, cli, ctr.ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"}) + _, tls15mqsc := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) + _, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"}) t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\" \n AutoConfig MQSC file contents %v\n 15-tls: %v", sslkeyROutput, autoMQSC, tls15mqsc) } } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Test with CA cert func TestSSLKEYRWithCACert(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -318,32 +296,35 @@ func TestSSLKEYRWithCACert(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDirWithCA(t, false) + ":/etc/mqm/pki/keys/QM1CA", }, - // Assign a random port for the web server on the host - PortBindings: nat.PortMap{ - "9443/tcp": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - }, - }, - }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + // Assign a random port for the web server on the host + var binding ce.PortBinding + ports := []int{9443} + for _, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // execute runmqsc to display qmgr SSLKEYR and CERTLABL attibutes. // Search the console output for exepcted values - _, sslkeyROutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) + _, sslkeyROutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) if !strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") { // Although queue manager is ready, it may be that MQSC scripts have not been applied yet. @@ -352,38 +333,35 @@ func TestSSLKEYRWithCACert(t *testing.T) { var i int for i = 0; i < waitCount; i++ { time.Sleep(1 * time.Second) - _, sslkeyROutput = execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) + _, sslkeyROutput = execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"}) if strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") { break } } // Failed to get expected output? dump the contents of mqsc files. if i == waitCount { - _, tls15mqsc := execContainer(t, cli, ctr.ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) - _, autoMQSC := execContainer(t, cli, ctr.ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"}) + _, tls15mqsc := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) + _, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"}) t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"\n AutoConfig MQSC file contents %v\n 15-tls: %v", sslkeyROutput, autoMQSC, tls15mqsc) } } if !strings.Contains(sslkeyROutput, "CERTLABL(QM1CA)") { - _, autoMQSC := execContainer(t, cli, ctr.ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) + _, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"}) t.Errorf("Expected CERTLABL to be 'QM1CA' but it is not; got \"%v\" \n MQSC File contents %v", sslkeyROutput, autoMQSC) } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Verifies SSLFIPS is set to NO if MQ_ENABLE_FIPS=false func TestSSLFIPSNO(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -392,24 +370,24 @@ func TestSSLFIPSNO(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes. // Search the console output for exepcted values - _, sslFIPSOutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) + _, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") { t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput) } @@ -422,7 +400,7 @@ func TestSSLFIPSNO(t *testing.T) { } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Verifies SSLFIPS is set to YES if certificates for queue manager @@ -430,13 +408,10 @@ func TestSSLFIPSNO(t *testing.T) { func TestSSLFIPSYES(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() appPassword := "differentPassw0rd" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_APP_PASSWORD=" + appPassword, @@ -446,30 +421,40 @@ func TestSSLFIPSYES(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + var binding ce.PortBinding + ports := []int{1414} + for _, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // Check for expected message on container log - logs := inspectLogs(t, cli, ctr.ID) + logs := inspectLogs(t, cli, ID) if !strings.Contains(logs, "FIPS cryptography is enabled.") { t.Errorf("Expected 'FIPS cryptography is enabled.' but got %v\n", logs) } // execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes. // Search the console output for exepcted values - _, sslFIPSOutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) + _, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") { t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput) } @@ -483,26 +468,23 @@ func TestSSLFIPSYES(t *testing.T) { t.Run("JMS", func(t *testing.T) { // Run the JMS tests, with no password specified - runJMSTests(t, cli, ctr.ID, true, "app", appPassword, "false", "TLS_RSA_WITH_AES_256_CBC_SHA256") + runJMSTests(t, cli, ID, true, "app", appPassword, "false", "TLS_RSA_WITH_AES_256_CBC_SHA256") }) // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // TestDevSecureFIPSYESWeb verifies if the MQ Web Server is running in FIPS mode func TestDevSecureFIPSTrueWeb(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() const tlsPassPhrase string = "passw0rd" qm := "qm1" appPassword := "differentPassw0rd" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + qm, @@ -514,65 +496,65 @@ func TestDevSecureFIPSTrueWeb(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", tlsDir(t, false) + ":/etc/mqm/pki/trust/default", }, - // Assign a random port for the web server on the host - // TODO: Don't do this for all tests - PortBindings: nat.PortMap{ - "9443/tcp": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - }, - }, - }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + // Assign a random port for the web server on the host + // TODO: Don't do this for all tests + var binding ce.PortBinding + ports := []int{9443} + for _, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) cert := filepath.Join(tlsDir(t, true), "server.crt") - waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase)) + waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase)) // Create a TLS Config with a cipher to use when connecting over HTTPS var secureTLSConfig *tls.Config = createTLSConfigWithCipher(t, cert, tlsPassPhrase, []uint16{tls.TLS_RSA_WITH_AES_256_GCM_SHA384}) // Put a message to queue t.Run("REST messaging", func(t *testing.T) { - testRESTMessaging(t, cli, ctr.ID, secureTLSConfig, qm, "app", appPassword, "") + testRESTMessaging(t, cli, ID, secureTLSConfig, qm, "app", appPassword, "") }) // Create a TLS Config with a non-FIPS cipher to use when connecting over HTTPS var secureNonFIPSCipherConfig *tls.Config = createTLSConfigWithCipher(t, cert, tlsPassPhrase, []uint16{tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}) // Put a message to queue - the attempt to put message will fail with a EOF return message. t.Run("REST messaging", func(t *testing.T) { - testRESTMessaging(t, cli, ctr.ID, secureNonFIPSCipherConfig, qm, "app", appPassword, "EOF") + testRESTMessaging(t, cli, ID, secureNonFIPSCipherConfig, qm, "app", appPassword, "EOF") }) // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // TestDevSecureNOFIPSWeb verifies if the MQ Web Server is not running in FIPS mode func TestDevSecureFalseFIPSWeb(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() const tlsPassPhrase string = "passw0rd" qm := "qm1" appPassword := "differentPassw0rd" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + qm, @@ -584,38 +566,41 @@ func TestDevSecureFalseFIPSWeb(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", tlsDir(t, false) + ":/etc/mqm/pki/trust/default", }, - // Assign a random port for the web server on the host - PortBindings: nat.PortMap{ - "9443/tcp": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - }, - }, - }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + // Assign a random port for the web server on the host + var binding ce.PortBinding + ports := []int{9443} + for _, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) cert := filepath.Join(tlsDir(t, true), "server.crt") - waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase)) + waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase)) // As FIPS is not enabled, the MQ WebServer (actually Java) will choose a JSSE provider from the list // specified in java.security file. We will need to enable java.net.debug and then parse the web server // logs to check what JJSE provider is being used. Hence just check the jvm.options file does not contain // -Dcom.ibm.jsse2.usefipsprovider line. - _, jvmOptionsOutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "cat /var/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults/jvm.options"}) + _, jvmOptionsOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "cat /var/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults/jvm.options"}) if strings.Contains(jvmOptionsOutput, "-Dcom.ibm.jsse2.usefipsprovider") { t.Errorf("Did not expect -Dcom.ibm.jsse2.usefipsprovider but it is not; got \"%v\"", jvmOptionsOutput) } @@ -623,24 +608,21 @@ func TestDevSecureFalseFIPSWeb(t *testing.T) { // Just do a HTTPS GET as well to query installation details. var secureTLSConfig *tls.Config = createTLSConfigWithCipher(t, cert, tlsPassPhrase, []uint16{tls.TLS_RSA_WITH_AES_256_GCM_SHA384}) t.Run("REST admin", func(t *testing.T) { - testRESTAdmin(t, cli, ctr.ID, secureTLSConfig, "") + testRESTAdmin(t, cli, ID, secureTLSConfig, "") }) // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Verify SSLFIPS is set to NO if no certificates were supplied func TestSSLFIPSTrueNoCerts(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() appPassword := "differentPassw0rd" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_APP_PASSWORD=" + appPassword, @@ -650,23 +632,23 @@ func TestSSLFIPSTrueNoCerts(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes. // Search the console output for exepcted values - _, sslFIPSOutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) + _, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) if !strings.Contains(sslFIPSOutput, "SSLKEYR( )") { t.Errorf("Expected SSLKEYR to be ' ' but it is not; got \"%v\"", sslFIPSOutput) } @@ -679,19 +661,16 @@ func TestSSLFIPSTrueNoCerts(t *testing.T) { } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Verifies SSLFIPS is set to NO if MQ_ENABLE_FIPS=tru (invalid value) func TestSSLFIPSInvalidValue(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -700,24 +679,24 @@ func TestSSLFIPSInvalidValue(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) + waitForReady(t, cli, ID) // execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes. // Search the console output for exepcted values - _, sslFIPSOutput := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) + _, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") { t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput) } @@ -731,19 +710,16 @@ func TestSSLFIPSInvalidValue(t *testing.T) { } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } // Container creation fails when invalid certs are passed and MQ_ENABLE_FIPS set true func TestSSLFIPSBadCerts(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -752,25 +728,25 @@ func TestSSLFIPSBadCerts(t *testing.T) { }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDirInvalid(t, false) + ":/etc/mqm/pki/keys/default", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) + defer cleanContainer(t, cli, ID) + startContainer(t, cli, ID) - rc := waitForContainer(t, cli, ctr.ID, 20*time.Second) + rc := waitForContainer(t, cli, ID, 20*time.Second) // Expect return code 1 if container failed to create. if rc == 1 { // Get container logs and search for specific message. - logs := inspectLogs(t, cli, ctr.ID) + logs := inspectLogs(t, cli, ID) if strings.Contains(logs, "Failed to parse private key") { t.Logf("Container creating failed because of invalid certifates") } @@ -780,5 +756,5 @@ func TestSSLFIPSBadCerts(t *testing.T) { } // Stop the container cleanly - stopContainer(t, cli, ctr.ID) + stopContainer(t, cli, ID) } diff --git a/test/docker/devconfig_test_util.go b/test/container/devconfig_test_util.go similarity index 85% rename from test/docker/devconfig_test_util.go rename to test/container/devconfig_test_util.go index 494a164..4ae732f 100644 --- a/test/docker/devconfig_test_util.go +++ b/test/container/devconfig_test_util.go @@ -2,7 +2,7 @@ // +build mqdev /* -© Copyright IBM Corporation 2018, 2022 +© Copyright IBM Corporation 2018, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -34,9 +34,7 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) const defaultAdminPassword string = "passw0rd" @@ -49,7 +47,7 @@ var insecureTLSConfig *tls.Config = &tls.Config{ InsecureSkipVerify: true, } -func waitForWebReady(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) { +func waitForWebReady(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config) { t.Logf("%s Waiting for web server to be ready", time.Now().Format(time.RFC3339)) httpClient := http.Client{ Timeout: time.Duration(10 * time.Second), @@ -57,7 +55,11 @@ func waitForWebReady(t *testing.T, cli *client.Client, ID string, tlsConfig *tls TLSClientConfig: tlsConfig, }, } - url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getPort(t, cli, ID, 9443)) + port, err := cli.GetContainerPort(ID, 9443) + if err != nil { + t.Fatal(err) + } + url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", port) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() @@ -91,11 +93,16 @@ func tlsDirInvalid(t *testing.T, unixPath bool) string { } // runJMSTests runs a container with a JMS client, which connects to the queue manager container with the specified ID -func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, password string, ibmjre string, cipherName string) { - containerConfig := container.Config{ +func runJMSTests(t *testing.T, cli ce.ContainerInterface, ID string, tls bool, user, password string, ibmjre string, cipherName string) { + port, err := cli.GetContainerPort(ID, 1414) + if err != nil { + t.Error(err) + } + containerConfig := ce.ContainerConfig{ // -e MQ_PORT_1414_TCP_ADDR=9.145.14.173 -e MQ_USERNAME=app -e MQ_PASSWORD=passw0rd -e MQ_CHANNEL=DEV.APP.SVRCONN -e MQ_TLS_TRUSTSTORE=/tls/test.p12 -e MQ_TLS_PASSPHRASE=passw0rd -v /Users/arthurbarr/go/src/github.com/ibm-messaging/mq-container/test/tls:/tls msgtest Env: []string{ - "MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID), + "MQ_PORT_1414_TCP_ADDR=127.0.0.1", + "MQ_PORT_1414_OVERRIDE=" + port, "MQ_USERNAME=" + user, "MQ_CHANNEL=DEV.APP.SVRCONN", "IBMJRE=" + ibmjre, @@ -114,26 +121,28 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa "MQ_TLS_CIPHER=" + cipherName, }...) } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDir(t, false) + ":/var/tls", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, strings.Replace(t.Name()+"JMS", "/", "", -1)) + networkingConfig := ce.ContainerNetworkSettings{ + Networks: []string{"host"}, + } + jmsID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, strings.Replace(t.Name()+"JMS", "/", "", -1)) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr.ID) - rc := waitForContainer(t, cli, ctr.ID, 2*time.Minute) + startContainer(t, cli, jmsID) + rc := waitForContainer(t, cli, jmsID, 2*time.Minute) if rc != 0 { t.Errorf("JUnit container failed with rc=%v", rc) } // Get console output of the container and process the lines // to see if we have any failures - scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, ctr.ID))) + scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, jmsID))) for scanner.Scan() { s := scanner.Text() if processJunitLogLine(s) { @@ -141,7 +150,7 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa } } - defer cleanContainer(t, cli, ctr.ID) + defer cleanContainer(t, cli, jmsID) } // Parse JUnit log line and return true if line contains failed or aborted tests @@ -205,14 +214,18 @@ func createTLSConfig(t *testing.T, certFile, password string) *tls.Config { } } -func testRESTAdmin(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config, errorExpected string) { +func testRESTAdmin(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config, errorExpected string) { httpClient := http.Client{ Timeout: time.Duration(30 * time.Second), Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, } - url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getPort(t, cli, ID, 9443)) + port, err := cli.GetContainerPort(ID, 9443) + if err != nil { + t.Fatal(err) + } + url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", port) req, err := http.NewRequest("GET", url, nil) req.SetBasicAuth("admin", defaultAdminPassword) resp, err := httpClient.Do(req) @@ -248,7 +261,7 @@ func logHTTPResponse(t *testing.T, resp *http.Response) { t.Logf("HTTP response: %v", string(d)) } -func testRESTMessaging(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config, qmName string, user string, password string, errorExpected string) { +func testRESTMessaging(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config, qmName string, user string, password string, errorExpected string) { httpClient := http.Client{ Timeout: time.Duration(30 * time.Second), Transport: &http.Transport{ @@ -256,7 +269,11 @@ func testRESTMessaging(t *testing.T, cli *client.Client, ID string, tlsConfig *t }, } q := "DEV.QUEUE.1" - url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/messaging/qmgr/%s/queue/%s/message", getPort(t, cli, ID, 9443), qmName, q) + port, err := cli.GetContainerPort(ID, 9443) + if err != nil { + t.Fatal(err) + } + url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/messaging/qmgr/%s/queue/%s/message", port, qmName, q) putMessage := []byte("Hello") req, err := http.NewRequest("POST", url, bytes.NewBuffer(putMessage)) req.SetBasicAuth(user, password) diff --git a/test/docker/docker_api_test.go b/test/container/docker_api_test.go similarity index 83% rename from test/docker/docker_api_test.go rename to test/container/docker_api_test.go index e2575fe..110d8e3 100644 --- a/test/docker/docker_api_test.go +++ b/test/container/docker_api_test.go @@ -18,7 +18,6 @@ package main import ( "bufio" "bytes" - "context" "encoding/json" "fmt" "os" @@ -29,23 +28,18 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) func TestLicenseNotSet(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{} + cli := ce.NewContainerClient() + + containerConfig := ce.ContainerConfig{} id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) - rc := waitForContainer(t, cli, id, 20*time.Second) + rc := waitForContainer(t, cli, id, 30*time.Second) if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } @@ -57,16 +51,14 @@ func TestLicenseNotSet(t *testing.T) { func TestLicenseView(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=view"}, } id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) - rc := waitForContainer(t, cli, id, 20*time.Second) + rc := waitForContainer(t, cli, id, 30*time.Second) if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } @@ -91,11 +83,9 @@ func TestGoldenPathNoMetrics(t *testing.T) { // Actual test function for TestGoldenPathNoMetrics & TestGoldenPathWithMetrics func goldenPath(t *testing.T, metrics bool) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } if metrics { @@ -128,11 +118,8 @@ func goldenPath(t *testing.T, metrics bool) { func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) { search := "QMNAME(" + expectedName + ")" - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept"}, Hostname: hostName, } @@ -175,13 +162,10 @@ func TestWithVolumeNoMetrics(t *testing.T) { // Actual test function for TestWithVolumeNoMetrics & TestWithVolumeAndMetrics func withVolume(t *testing.T, metric bool) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() vol := createVolume(t, cli, t.Name()) - defer removeVolume(t, cli, vol.Name) - containerConfig := container.Config{ + defer removeVolume(t, cli, vol) + containerConfig := ce.ContainerConfig{ Image: imageName(), Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } @@ -189,48 +173,48 @@ func withVolume(t *testing.T, metric bool) { containerConfig.Env = append(containerConfig.Env, "MQ_ENABLE_METRICS=true") } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), - vol.Name + ":/mnt/mqm", + vol + ":/mnt/mqm", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr.ID) + startContainer(t, cli, ID) // TODO: If this test gets an error waiting for readiness, the first container might not get cleaned up - waitForReady(t, cli, ctr.ID) + waitForReady(t, cli, ID) // Delete the first container - cleanContainer(t, cli, ctr.ID) + cleanContainer(t, cli, ID) // Start a new container with the same volume - ctr2, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + ID2, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr2.ID) - startContainer(t, cli, ctr2.ID) - waitForReady(t, cli, ctr2.ID) + defer cleanContainer(t, cli, ID2) + startContainer(t, cli, ID2) + waitForReady(t, cli, ID2) } // TestWithSplitVolumesLogsData starts a queue manager with separate log/data mounts func TestWithSplitVolumesLogsData(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + + qmsharedlogs := createVolume(t, cli, "qmsharedlogs") + defer removeVolume(t, cli, qmsharedlogs) + qmshareddata := createVolume(t, cli, "qmshareddata") + defer removeVolume(t, cli, qmshareddata) + + err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) if err != nil { t.Fatal(err) } - qmsharedlogs := createVolume(t, cli, "qmsharedlogs") - defer removeVolume(t, cli, qmsharedlogs.Name) - qmshareddata := createVolume(t, cli, "qmshareddata") - defer removeVolume(t, cli, qmshareddata.Name) - - err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) - defer removeVolume(t, cli, qmVol) defer cleanContainer(t, cli, qmID) @@ -239,16 +223,16 @@ func TestWithSplitVolumesLogsData(t *testing.T) { // TestWithSplitVolumesLogsOnly starts a queue manager with a separate log mount func TestWithSplitVolumesLogsOnly(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + + qmsharedlogs := createVolume(t, cli, "qmsharedlogs") + defer removeVolume(t, cli, qmsharedlogs) + + err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, "", []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) if err != nil { t.Fatal(err) } - qmsharedlogs := createVolume(t, cli, "qmsharedlogs") - defer removeVolume(t, cli, qmsharedlogs.Name) - - err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) - defer removeVolume(t, cli, qmVol) defer cleanContainer(t, cli, qmID) @@ -257,16 +241,16 @@ func TestWithSplitVolumesLogsOnly(t *testing.T) { // TestWithSplitVolumesDataOnly starts a queue manager with a separate data mount func TestWithSplitVolumesDataOnly(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + + qmshareddata := createVolume(t, cli, "qmshareddata") + defer removeVolume(t, cli, qmshareddata) + + err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) if err != nil { t.Fatal(err) } - qmshareddata := createVolume(t, cli, "qmshareddata") - defer removeVolume(t, cli, qmshareddata.Name) - - err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}) - defer removeVolume(t, cli, qmVol) defer cleanContainer(t, cli, qmID) @@ -278,11 +262,8 @@ func TestWithSplitVolumesDataOnly(t *testing.T) { func TestNoVolumeWithRestart(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } id := runContainer(t, cli, &containerConfig) @@ -298,73 +279,78 @@ func TestNoVolumeWithRestart(t *testing.T) { // where `runmqserver -i` is run to initialize the storage. Then the // container can be run as normal. func TestVolumeRequiresRoot(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) + cli := ce.NewContainerClient() + + // ":nocopy" requires podman version 4.2 + if cli.ContainerTool == "podman" && cli.Version < "4.2.0" { + t.Skipf("Skipping as 'nocopy' is not available before podman version 4.2.0. Detected podman version %v", cli.Version) } + vol := createVolume(t, cli, t.Name()) - defer removeVolume(t, cli, vol.Name) + defer removeVolume(t, cli, vol) // Set permissions on the volume to only allow root to write it // It's important that read and execute permissions are given to other users - rc, _ := runContainerOneShotWithVolume(t, cli, vol.Name+":/mnt/mqm:nocopy", "bash", "-c", "chown 65534:4294967294 /mnt/mqm/ && chmod 0755 /mnt/mqm/ && ls -lan /mnt/mqm/") + // This test was previously using nobody:nogroup and is now using nobody:nobody for compatibility + rc, _ := runContainerOneShotWithVolume(t, cli, vol+":/mnt/mqm:nocopy", "chown", "nobody:nobody", "/mnt/mqm/") if rc != 0 { - t.Errorf("Expected one shot container to return rc=0, got rc=%v", rc) + t.Fatalf("Expected one shot container to return rc=0, got rc=%v", rc) + } + rc, _ = runContainerOneShotWithVolume(t, cli, vol+":/mnt/mqm:nocopy", "chmod", "0755", "/mnt/mqm/") + if rc != 0 { + t.Fatalf("Expected one shot container to return rc=0, got rc=%v", rc) } - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Image: imageName(), Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), - vol.Name + ":/mnt/mqm:nocopy", + vol + ":/mnt/mqm:nocopy", }, } - networkingConfig := network.NetworkingConfig{} + networkingConfig := ce.ContainerNetworkSettings{} // Run an "init container" as root, with the "-i" option, to initialize the volume - containerConfig = container.Config{ + containerConfig = ce.ContainerConfig{ Image: imageName(), Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"}, User: "0", Entrypoint: []string{"runmqserver", "-i"}, } - initCtr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Init") + initCtrID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"Init") if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, initCtr.ID) - t.Logf("Init container ID=%v", initCtr.ID) - startContainer(t, cli, initCtr.ID) - rc = waitForContainer(t, cli, initCtr.ID, 20*time.Second) + defer cleanContainer(t, cli, initCtrID) + t.Logf("Init container ID=%v", initCtrID) + startContainer(t, cli, initCtrID) + rc = waitForContainer(t, cli, initCtrID, 30*time.Second) if rc != 0 { t.Errorf("Expected init container to exit with rc=0, got rc=%v", rc) } - containerConfig = container.Config{ + containerConfig = ce.ContainerConfig{ Image: imageName(), Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"}, } - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Main") + ctrID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"Main") if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - t.Logf("Main container ID=%v", ctr.ID) - startContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + defer cleanContainer(t, cli, ctrID) + t.Logf("Main container ID=%v", ctrID) + startContainer(t, cli, ctrID) + waitForReady(t, cli, ctrID) } // TestCreateQueueManagerFail causes a failure of `crtmqm` func TestCreateQueueManagerFail(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -377,13 +363,13 @@ func TestCreateQueueManagerFail(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) - rc := waitForContainer(t, cli, id, 10*time.Second) + rc := waitForContainer(t, cli, id, 30*time.Second) if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } @@ -394,10 +380,7 @@ func TestCreateQueueManagerFail(t *testing.T) { func TestStartQueueManagerFail(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -410,13 +393,13 @@ func TestStartQueueManagerFail(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) - rc := waitForContainer(t, cli, id, 20*time.Second) + rc := waitForContainer(t, cli, id, 30*time.Second) if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } @@ -430,46 +413,43 @@ func TestStartQueueManagerFail(t *testing.T) { func TestVolumeUnmount(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() vol := createVolume(t, cli, t.Name()) - defer removeVolume(t, cli, vol.Name) - containerConfig := container.Config{ + defer removeVolume(t, cli, vol) + containerConfig := ce.ContainerConfig{ Image: imageName(), Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ // SYS_ADMIN capability is required to unmount file systems CapAdd: []string{ "SYS_ADMIN", }, Binds: []string{ coverageBind(t), - vol.Name + ":/mnt/mqm", + vol + ":/mnt/mqm", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ctrID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr.ID) - defer cleanContainer(t, cli, ctr.ID) - waitForReady(t, cli, ctr.ID) + startContainer(t, cli, ctrID) + defer cleanContainer(t, cli, ctrID) + waitForReady(t, cli, ctrID) // Unmount the volume as root - rc, out := execContainer(t, cli, ctr.ID, "root", []string{"umount", "-l", "-f", "/mnt/mqm"}) + rc, out := execContainer(t, cli, ctrID, "root", []string{"umount", "-l", "/mnt/mqm"}) if rc != 0 { t.Fatalf("Expected umount to work with rc=0, got %v. Output was: %s", rc, out) } time.Sleep(3 * time.Second) - rc, _ = execContainer(t, cli, ctr.ID, "", []string{"chkmqhealthy"}) + rc, _ = execContainer(t, cli, ctrID, "", []string{"chkmqhealthy"}) if rc == 0 { t.Errorf("Expected chkmqhealthy to fail") - _, df := execContainer(t, cli, ctr.ID, "", []string{"df"}) + _, df := execContainer(t, cli, ctrID, "", []string{"df"}) t.Logf(df) - _, ps := execContainer(t, cli, ctr.ID, "", []string{"ps", "-ef"}) + _, ps := execContainer(t, cli, ctrID, "", []string{"ps", "-ef"}) t.Logf(ps) } } @@ -479,16 +459,10 @@ func TestVolumeUnmount(t *testing.T) { func TestZombies(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ - Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"}, - //ExposedPorts: ports, - ExposedPorts: nat.PortSet{ - "1414/tcp": struct{}{}, - }, + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ + Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"}, + ExposedPorts: []string{"1414/tcp"}, } id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) @@ -516,10 +490,7 @@ func TestZombies(t *testing.T) { func TestMQSC(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -535,7 +506,7 @@ func TestMQSC(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -563,10 +534,7 @@ func TestMQSC(t *testing.T) { func TestLargeMQSC(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() const numQueues = 1000 var buf bytes.Buffer for i := 1; i <= numQueues; i++ { @@ -587,7 +555,7 @@ func TestLargeMQSC(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -615,10 +583,7 @@ func TestLargeMQSC(t *testing.T) { func TestRedactValidMQSC(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var buf bytes.Buffer passwords := "hippoman4567" sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords) @@ -667,7 +632,7 @@ func TestRedactValidMQSC(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -682,7 +647,7 @@ func TestRedactValidMQSC(t *testing.T) { t.Fatalf("Expected redacted MQSC output, got: %v", s) } } - err = scanner.Err() + err := scanner.Err() if err != nil { t.Fatal(err) } @@ -693,10 +658,7 @@ func TestRedactValidMQSC(t *testing.T) { func TestRedactInvalidMQSC(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var buf bytes.Buffer passwords := "hippoman4567" sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords) @@ -739,13 +701,13 @@ func TestRedactInvalidMQSC(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } id := runContainer(t, cli, &containerConfig) defer cleanContainer(t, cli, id) - rc := waitForContainer(t, cli, id, 20*time.Second) + rc := waitForContainer(t, cli, id, 30*time.Second) if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } @@ -756,7 +718,7 @@ func TestRedactInvalidMQSC(t *testing.T) { t.Fatalf("Expected redacted MQSC output, got: %v", s) } } - err = scanner.Err() + err := scanner.Err() if err != nil { t.Fatal(err) } @@ -766,10 +728,7 @@ func TestRedactInvalidMQSC(t *testing.T) { // tries to start a container based on that image, and checks that container terminates func TestInvalidMQSC(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -785,7 +744,7 @@ func TestInvalidMQSC(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -800,10 +759,7 @@ func TestInvalidMQSC(t *testing.T) { func TestSimpleMQIniMerge(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -819,7 +775,7 @@ func TestSimpleMQIniMerge(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -838,10 +794,7 @@ func TestSimpleMQIniMerge(t *testing.T) { } func TestMultipleIniMerge(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var files = []struct { Name, Body string }{ @@ -865,7 +818,7 @@ func TestMultipleIniMerge(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Image: tag, } @@ -887,10 +840,7 @@ func TestMultipleIniMerge(t *testing.T) { } func TestMQIniMergeOnTheSameVolumeButTwoContainers(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() var filesFirstContainer = []struct { Name, Body string @@ -907,37 +857,37 @@ func TestMQIniMergeOnTheSameVolumeButTwoContainers(t *testing.T) { firstImage := createImage(t, cli, filesFirstContainer) defer deleteImage(t, cli, firstImage) vol := createVolume(t, cli, t.Name()) - defer removeVolume(t, cli, vol.Name) + defer removeVolume(t, cli, vol) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Image: firstImage, Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), - vol.Name + ":/mnt/mqm", + vol + ":/mnt/mqm", }, } - networkingConfig := network.NetworkingConfig{} - ctr1, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ctr1ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr1.ID) - waitForReady(t, cli, ctr1.ID) + startContainer(t, cli, ctr1ID) + waitForReady(t, cli, ctr1ID) catIniFileCommand := fmt.Sprintf("cat /var/mqm/qmgrs/qm1/qm.ini") - _, test := execContainer(t, cli, ctr1.ID, "", []string{"bash", "-c", catIniFileCommand}) + _, test := execContainer(t, cli, ctr1ID, "", []string{"bash", "-c", catIniFileCommand}) addedStanza := strings.Contains(test, "ApplicationTrace:\n ApplName=amqsact*\n Trace=OFF") if addedStanza != true { t.Error("ERROR: The Files are not merged correctly") } // Delete the first container - cleanContainer(t, cli, ctr1.ID) + cleanContainer(t, cli, ctr1ID) var filesSecondContainer = []struct { Name, Body string @@ -955,20 +905,20 @@ func TestMQIniMergeOnTheSameVolumeButTwoContainers(t *testing.T) { secondImage := createImage(t, cli, filesSecondContainer) defer deleteImage(t, cli, secondImage) - containerConfig2 := container.Config{ + containerConfig2 := ce.ContainerConfig{ Image: secondImage, Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } - ctr2, err := cli.ContainerCreate(context.Background(), &containerConfig2, &hostConfig, &networkingConfig, t.Name()) + ctr2ID, err := cli.ContainerCreate(&containerConfig2, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr2.ID) - startContainer(t, cli, ctr2.ID) - waitForReady(t, cli, ctr2.ID) + defer cleanContainer(t, cli, ctr2ID) + startContainer(t, cli, ctr2ID) + waitForReady(t, cli, ctr2ID) - _, test2 := execContainer(t, cli, ctr2.ID, "", []string{"bash", "-c", catIniFileCommand}) + _, test2 := execContainer(t, cli, ctr2ID, "", []string{"bash", "-c", catIniFileCommand}) changedStanza := strings.Contains(test2, "LogBufferPages=128") //check if stanza that was merged in the first container doesnt exist in this one. firstMergedStanza := strings.Contains(test2, "ApplicationTrace:\n ApplName=amqsact*\n Trace=OFF") @@ -985,10 +935,7 @@ func TestMQIniMergeOnTheSameVolumeButTwoContainers(t *testing.T) { func TestReadiness(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() const numQueues = 3 var buf bytes.Buffer for i := 1; i <= numQueues; i++ { @@ -1009,7 +956,7 @@ func TestReadiness(t *testing.T) { tag := createImage(t, cli, files) defer deleteImage(t, cli, tag) - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=1"}, Image: tag, } @@ -1048,10 +995,7 @@ func TestErrorLogRotation(t *testing.T) { t.Skipf("Skipping %v until test defect fixed", t.Name()) t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() logsize := 65536 @@ -1062,7 +1006,7 @@ func TestErrorLogRotation(t *testing.T) { } qmName := "qm1" - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + qmName, @@ -1070,9 +1014,7 @@ func TestErrorLogRotation(t *testing.T) { "LOG_FORMAT=json", fmt.Sprintf("AMQ_EXTRA_QM_STANZAS=QMErrorLog:ErrorLogSize=%d", logsize), }, - ExposedPorts: nat.PortSet{ - "1414/tcp": struct{}{}, - }, + ExposedPorts: []string{"1414/tcp"}, } id := runContainer(t, cli, &containerConfig) @@ -1110,7 +1052,7 @@ func TestErrorLogRotation(t *testing.T) { totalMirrored++ } } - err = scanner.Err() + err := scanner.Err() if err != nil { t.Fatal(err) } @@ -1138,11 +1080,8 @@ func TestJSONLogFormatNoMetrics(t *testing.T) { // Actual test function for TestJSONLogFormatWithMetrics & TestJSONLogFormatNoMetrics func jsonLogFormat(t *testing.T, metric bool) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "LOG_FORMAT=json", @@ -1165,7 +1104,7 @@ func jsonLogFormat(t *testing.T, metric bool) { t.Fatalf("Expected all log lines to be valid JSON. Got error %v for line %v", err, s) } } - err = scanner.Err() + err := scanner.Err() if err != nil { t.Fatal(err) } @@ -1176,11 +1115,8 @@ func jsonLogFormat(t *testing.T, metric bool) { func TestMQJSONDisabled(t *testing.T) { t.SkipNow() t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1204,12 +1140,9 @@ func TestCorrectLicense(t *testing.T) { t.Fatal("Required test environment variable 'EXPECTED_LICENSE' was not set.") } - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept"}, } id := runContainer(t, cli, &containerConfig) @@ -1220,6 +1153,7 @@ func TestCorrectLicense(t *testing.T) { if rc != 0 { t.Fatalf("Failed to get license string. RC=%d. Output=%s", rc, license) } + license = ce.SanitizeString(license) if license != expectedLicense { t.Errorf("Expected license to be '%s' but was '%s", expectedLicense, license) @@ -1229,12 +1163,9 @@ func TestCorrectLicense(t *testing.T) { func TestVersioning(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept"}, } id := runContainer(t, cli, &containerConfig) @@ -1350,12 +1281,9 @@ func TestVersioning(t *testing.T) { func TestTraceStrmqm(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_ENABLE_TRACE_STRMQM=1", @@ -1375,11 +1303,8 @@ func TestTraceStrmqm(t *testing.T) { // privileges enabled or disabled. Otherwise the same as the golden path tests. func utilTestHealthCheck(t *testing.T, nonewpriv bool) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } hostConfig := getDefaultHostConfig(t, cli) @@ -1404,7 +1329,7 @@ func TestHealthCheckWithNoNewPrivileges(t *testing.T) { utilTestHealthCheck(t, true) } -// TestHealthCheckWithNoNewPrivileges tests golden path start/stop plus +// TestHealthCheckWithNewPrivileges tests golden path start/stop plus // chkmqhealthy when running in a container where new privileges are // allowed (i.e. setuid is allowed) // See https://github.com/ibm-messaging/mq-container/issues/428 @@ -1416,11 +1341,8 @@ func TestHealthCheckWithNewPrivileges(t *testing.T) { // privileges enabled or disabled. Otherwise the same as the golden path tests. func utilTestStartedCheck(t *testing.T, nonewpriv bool) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, } hostConfig := getDefaultHostConfig(t, cli) @@ -1445,7 +1367,7 @@ func TestStartedCheckWithNoNewPrivileges(t *testing.T) { utilTestStartedCheck(t, true) } -// TestStartedCheckWithNoNewPrivileges tests golden path start/stop plus +// TestStartedCheckWithNewPrivileges tests golden path start/stop plus // chkmqstarted when running in a container where new privileges are // allowed (i.e. setuid is allowed) // See https://github.com/ibm-messaging/mq-container/issues/428 @@ -1457,11 +1379,8 @@ func TestStartedCheckWithNewPrivileges(t *testing.T) { // Check that when the container is stopped that the command endmqm has option -tp and x func TestEndMQMOpts(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_GRACE_PERIOD=27"}, } @@ -1480,11 +1399,8 @@ func TestEndMQMOpts(t *testing.T) { // Check that the number of logfilepages matches. func TestCustomLogFilePages(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{"LICENSE=accept", "MQ_QMGR_LOG_FILE_PAGES=8192", "MQ_QMGR_NAME=qmlfp"}, } @@ -1500,11 +1416,8 @@ func TestCustomLogFilePages(t *testing.T) { func TestLoggingConsoleSource(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1534,11 +1447,8 @@ func TestLoggingConsoleSource(t *testing.T) { func TestOldBehaviorWebConsole(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1565,7 +1475,7 @@ func TestOldBehaviorWebConsole(t *testing.T) { isValidJSON := checkLogForValidJSON(jsonLogs) if !isValidJSON { - t.Fatalf("Expected all log lines to be valid JSON. But got error %v ", err) + t.Fatalf("Expected all log lines to be valid JSON. Logs: %v ", jsonLogs) } // Stop the container cleanly @@ -1578,11 +1488,8 @@ func TestLoggingConsoleWithContRestart(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1633,11 +1540,8 @@ func TestLoggingWithQmgrAndExcludeId(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1661,7 +1565,7 @@ func TestLoggingWithQmgrAndExcludeId(t *testing.T) { isValidJSON := checkLogForValidJSON(jsonLogs) if !isValidJSON { - t.Fatalf("Expected all log lines to be valid JSON. But got error %v ", err) + t.Fatalf("Expected all log lines to be valid JSON. Logs: %v ", jsonLogs) } if strings.Contains(jsonLogs, "AMQ7230I") || strings.Contains(jsonLogs, "CWWKF0011I") { @@ -1693,11 +1597,8 @@ func TestLoggingWithQmgrAndExcludeId(t *testing.T) { func TestLoggingConsoleSetToWeb(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1735,11 +1636,8 @@ func TestLoggingConsoleSetToWeb(t *testing.T) { // json and check that log is in json format func TestLoggingConsoleSetToQmgr(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - containerConfig := container.Config{ + cli := ce.NewContainerClient() + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=qm1", @@ -1767,12 +1665,11 @@ func TestLoggingConsoleSetToQmgr(t *testing.T) { isValidJSON := checkLogForValidJSON(jsonLogs) if !isValidJSON { - t.Fatalf("Expected all log lines to be valid JSON. But got error %v ", err) + t.Fatalf("Expected all log lines to be valid JSON. Logs: %v ", jsonLogs) } // Stop the container cleanly stopContainer(t, cli, id) - } // Test queue manager with both personal and CA certificate having the same DN @@ -1821,12 +1718,9 @@ func scanForText(output string, prefix string, findText string) (int, bool) { func utilSubDNTest(t *testing.T, certPath string, overrideFlag string, expecteOutPut string, waitLong bool) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - containerConfig := container.Config{ + containerConfig := ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -1834,24 +1728,24 @@ func utilSubDNTest(t *testing.T, certPath string, overrideFlag string, expecteOu }, Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), tlsDirDN(t, false, certPath) + ":/etc/mqm/pki/keys/QM1", }, } - networkingConfig := network.NetworkingConfig{} - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + networkingConfig := ce.ContainerNetworkSettings{} + ctrID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - defer cleanContainer(t, cli, ctr.ID) - startContainer(t, cli, ctr.ID) + defer cleanContainer(t, cli, ctrID) + startContainer(t, cli, ctrID) if waitLong { - waitForReady(t, cli, ctr.ID) - _, output := execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) + waitForReady(t, cli, ctrID) + _, output := execContainer(t, cli, ctrID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"}) if !strings.Contains(output, "SSLKEYR(/run/runmqserver/tls/key)") { t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", output) } @@ -1859,7 +1753,7 @@ func utilSubDNTest(t *testing.T, certPath string, overrideFlag string, expecteOu if !strings.Contains(output, "CERTLABL(QM1)") { t.Errorf("Expected CERTLABL to be 'default' but it is not; got \"%v\"", output) } - _, output = execContainer(t, cli, ctr.ID, "", []string{"bash", "-c", "runmqakm -cert -list -type cms -db /run/runmqserver/tls/key.kdb -stashed"}) + _, output = execContainer(t, cli, ctrID, "", []string{"bash", "-c", "runmqakm -cert -list -type cms -db /run/runmqserver/tls/key.kdb -stashed"}) if strings.EqualFold(t.Name(), "TestWithCASignedCerts") { // There should be one personal certificate and one trusted certificate. count, found := scanForText(output, "!", "CN=MQMFTQM,OU=ISL,O=IBM,L=BLR,ST=KA,C=IN") @@ -1885,11 +1779,11 @@ func utilSubDNTest(t *testing.T, certPath string, overrideFlag string, expecteOu } } } else { - rc := waitForContainer(t, cli, ctr.ID, 20*time.Second) + rc := waitForContainer(t, cli, ctrID, 30*time.Second) // Expect return code 1 if container failed to create. if rc == 1 { // Get container logs and search for specific message. - logs := inspectLogs(t, cli, ctr.ID) + logs := inspectLogs(t, cli, ctrID) if !strings.Contains(logs, expecteOutPut) { t.Errorf("Container creating failed because of invalid certifates") } diff --git a/test/docker/docker_api_test_util.go b/test/container/docker_api_test_util.go similarity index 58% rename from test/docker/docker_api_test_util.go rename to test/container/docker_api_test_util.go index c7f11e0..89e4cc5 100644 --- a/test/docker/docker_api_test_util.go +++ b/test/container/docker_api_test_util.go @@ -34,28 +34,9 @@ import ( "testing" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/go-connections/nat" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) -type containerDetails struct { - ID string - Name string - Image string - Path string - Args []string - CapAdd []string - CapDrop []string - User string - Env []string -} - func imageName() string { image, ok := os.LookupEnv("TEST_IMAGE") if !ok { @@ -73,7 +54,7 @@ func imageNameDevJMS() string { } // baseImage returns the ID of the underlying base image (e.g. "ubuntu" or "rhel") -func baseImage(t *testing.T, cli *client.Client) string { +func baseImage(t *testing.T, cli ce.ContainerInterface) string { rc, out := runContainerOneShot(t, cli, "grep", "^ID=", "/etc/os-release") if rc != 0 { t.Fatal("Couldn't determine base image") @@ -87,7 +68,7 @@ func baseImage(t *testing.T, cli *client.Client) string { // devImage returns true if the image under test is a developer image, // determined by use of the MQ_ADMIN_PASSWORD environment variable -func devImage(t *testing.T, cli *client.Client) bool { +func devImage(t *testing.T, cli ce.ContainerInterface) bool { rc, _ := runContainerOneShot(t, cli, "printenv", "MQ_ADMIN_PASSWORD") if rc == 0 { return true @@ -107,6 +88,11 @@ func isWSL(t *testing.T) bool { return false } +// isARM returns whether we are running an arm64 MacOS machine +func isARM(t *testing.T) bool { + return runtime.GOARCH == "arm64" +} + // getCwd returns the working directory, in an os-specific or UNIX form func getCwd(t *testing.T, unixPath bool) string { dir, err := os.Getwd() @@ -161,29 +147,17 @@ func getTempDir(t *testing.T, unixStylePath bool) string { } // terminationMessage return the termination message, or an empty string if not set -func terminationMessage(t *testing.T, cli *client.Client, ID string) string { - r, _, err := cli.CopyFromContainer(context.Background(), ID, "/run/termination-log") +func terminationMessage(t *testing.T, cli ce.ContainerInterface, ID string) string { + r, err := cli.CopyFromContainer(ID, "/run/termination-log") if err != nil { t.Log(err) + t.Log(string(r)) return "" } - b, err := ioutil.ReadAll(r) - tr := tar.NewReader(bytes.NewReader(b)) - _, err = tr.Next() - if err != nil { - t.Log(err) - return "" - } - // read the complete content of the file h.Name into the bs []byte - content, err := ioutil.ReadAll(tr) - if err != nil { - t.Log(err) - return "" - } - return string(content) + return string(r) } -func expectTerminationMessage(t *testing.T, cli *client.Client, ID string) { +func expectTerminationMessage(t *testing.T, cli ce.ContainerInterface, ID string) { m := terminationMessage(t, cli, ID) if m == "" { t.Error("Expected termination message to be set") @@ -191,10 +165,10 @@ func expectTerminationMessage(t *testing.T, cli *client.Client, ID string) { } // logContainerDetails logs selected details about the container -func logContainerDetails(t *testing.T, cli *client.Client, ID string) { - i, err := cli.ContainerInspect(context.Background(), ID) +func logContainerDetails(t *testing.T, cli ce.ContainerInterface, ID string) { + i, err := cli.ContainerInspect(ID) if err == nil { - d := containerDetails{ + d := ce.ContainerDetailsLogging{ ID: ID, Name: i.Name, Image: i.Image, @@ -210,29 +184,29 @@ func logContainerDetails(t *testing.T, cli *client.Client, ID string) { } } -func cleanContainerQuiet(t *testing.T, cli *client.Client, ID string) { +func cleanContainerQuiet(t *testing.T, cli ce.ContainerInterface, ID string) { timeout := 10 * time.Second - err := cli.ContainerStop(context.Background(), ID, &timeout) + err := cli.ContainerStop(ID, &timeout) if err != nil { // Just log the error and continue t.Log(err) } - opts := types.ContainerRemoveOptions{ + opts := ce.ContainerRemoveOptions{ RemoveVolumes: true, Force: true, } - err = cli.ContainerRemove(context.Background(), ID, opts) + err = cli.ContainerRemove(ID, opts) if err != nil { t.Error(err) } } -func cleanContainer(t *testing.T, cli *client.Client, ID string) { +func cleanContainer(t *testing.T, cli ce.ContainerInterface, ID string) { logContainerDetails(t, cli, ID) t.Logf("Stopping container: %v", ID) timeout := 10 * time.Second // Stop the container. This allows the coverage output to be generated. - err := cli.ContainerStop(context.Background(), ID, &timeout) + err := cli.ContainerStop(ID, &timeout) if err != nil { // Just log the error and continue t.Log(err) @@ -250,11 +224,11 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) { } t.Logf("Removing container: %s", ID) - opts := types.ContainerRemoveOptions{ + opts := ce.ContainerRemoveOptions{ RemoveVolumes: true, Force: true, } - err = cli.ContainerRemove(context.Background(), ID, opts) + err = cli.ContainerRemove(ID, opts) if err != nil { t.Error(err) } @@ -268,12 +242,12 @@ func generateRandomUID() string { } // getDefaultHostConfig creates a HostConfig and populates it with the defaults used in testing -func getDefaultHostConfig(t *testing.T, cli *client.Client) *container.HostConfig { - hostConfig := container.HostConfig{ +func getDefaultHostConfig(t *testing.T, cli ce.ContainerInterface) *ce.ContainerHostConfig { + hostConfig := ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), }, - PortBindings: nat.PortMap{}, + PortBindings: []ce.PortBinding{}, CapDrop: []string{ "ALL", }, @@ -292,7 +266,7 @@ func getDefaultHostConfig(t *testing.T, cli *client.Client) *container.HostConfi // runContainerWithHostConfig creates and starts a container, using the supplied HostConfig. // Note that a default HostConfig can be created using getDefaultHostConfig. -func runContainerWithHostConfig(t *testing.T, cli *client.Client, containerConfig *container.Config, hostConfig *container.HostConfig) string { +func runContainerWithHostConfig(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig) string { if containerConfig.Image == "" { containerConfig.Image = imageName() } @@ -303,19 +277,19 @@ func runContainerWithHostConfig(t *testing.T, cli *client.Client, containerConfi // if coverage containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov") containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t)) - networkingConfig := network.NetworkingConfig{} + networkingConfig := ce.ContainerNetworkSettings{} t.Logf("Running container (%s)", containerConfig.Image) - ctr, err := cli.ContainerCreate(context.Background(), containerConfig, hostConfig, &networkingConfig, t.Name()) + ID, err := cli.ContainerCreate(containerConfig, hostConfig, &networkingConfig, t.Name()) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr.ID) - return ctr.ID + startContainer(t, cli, ID) + return ID } // runContainerWithAllConfig creates and starts a container, using the supplied ContainerConfig, HostConfig, // NetworkingConfig, and container name (or the value of t.Name if containerName=""). -func runContainerWithAllConfig(t *testing.T, cli *client.Client, containerConfig *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) string { +func runContainerWithAllConfig(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig, networkingConfig *ce.ContainerNetworkSettings, containerName string) string { if containerName == "" { containerName = t.Name() } @@ -330,26 +304,27 @@ func runContainerWithAllConfig(t *testing.T, cli *client.Client, containerConfig containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov") containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t)) t.Logf("Running container (%s)", containerConfig.Image) - ctr, err := cli.ContainerCreate(context.Background(), containerConfig, hostConfig, networkingConfig, containerName) + ID, err := cli.ContainerCreate(containerConfig, hostConfig, networkingConfig, containerName) if err != nil { t.Fatal(err) } - startContainer(t, cli, ctr.ID) - return ctr.ID + startContainer(t, cli, ID) + return ID } // runContainerWithPorts creates and starts a container, exposing the specified ports on the host. // If no image is specified in the container config, then the image name is retrieved from the TEST_IMAGE // environment variable. -func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *container.Config, ports []int) string { +func runContainerWithPorts(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, ports []int) string { hostConfig := getDefaultHostConfig(t, cli) + var binding ce.PortBinding for _, p := range ports { - port := nat.Port(fmt.Sprintf("%v/tcp", p)) - hostConfig.PortBindings[port] = []nat.PortBinding{ - { - HostIP: "0.0.0.0", - }, + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) } return runContainerWithHostConfig(t, cli, containerConfig, hostConfig) } @@ -357,112 +332,112 @@ func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *co // runContainer creates and starts a container. If no image is specified in // the container config, then the image name is retrieved from the TEST_IMAGE // environment variable. -func runContainer(t *testing.T, cli *client.Client, containerConfig *container.Config) string { +func runContainer(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig) string { return runContainerWithPorts(t, cli, containerConfig, nil) } // runContainerOneShot runs a container with a custom entrypoint, as the root // user and with default capabilities -func runContainerOneShot(t *testing.T, cli *client.Client, command ...string) (int64, string) { - containerConfig := container.Config{ +func runContainerOneShot(t *testing.T, cli ce.ContainerInterface, command ...string) (int64, string) { + containerConfig := ce.ContainerConfig{ Entrypoint: command, User: "root", Image: imageName(), } - hostConfig := container.HostConfig{} - networkingConfig := network.NetworkingConfig{} + hostConfig := ce.ContainerHostConfig{} + networkingConfig := ce.ContainerNetworkSettings{} t.Logf("Running one shot container (%s): %v", containerConfig.Image, command) - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShot") + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShot") if err != nil { t.Fatal(err) } - startOptions := types.ContainerStartOptions{} - err = cli.ContainerStart(context.Background(), ctr.ID, startOptions) + startOptions := ce.ContainerStartOptions{} + err = cli.ContainerStart(ID, startOptions) if err != nil { t.Fatal(err) } - defer cleanContainerQuiet(t, cli, ctr.ID) - rc := waitForContainer(t, cli, ctr.ID, 20*time.Second) - out := inspectLogs(t, cli, ctr.ID) + defer cleanContainerQuiet(t, cli, ID) + rc := waitForContainer(t, cli, ID, 20*time.Second) + out := inspectLogs(t, cli, ID) t.Logf("One shot container finished with rc=%v, output=%v", rc, out) return rc, out } // runContainerOneShot runs a container with a custom entrypoint, as the root // user, with default capabilities, and a volume mounted -func runContainerOneShotWithVolume(t *testing.T, cli *client.Client, bind string, command ...string) (int64, string) { - containerConfig := container.Config{ +func runContainerOneShotWithVolume(t *testing.T, cli ce.ContainerInterface, bind string, command ...string) (int64, string) { + containerConfig := ce.ContainerConfig{ Entrypoint: command, User: "root", Image: imageName(), } - hostConfig := container.HostConfig{ + hostConfig := ce.ContainerHostConfig{ Binds: []string{ bind, }, } - networkingConfig := network.NetworkingConfig{} + networkingConfig := ce.ContainerNetworkSettings{} t.Logf("Running one shot container with volume (%s): %v", containerConfig.Image, command) - ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShotVolume") + ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShotVolume") if err != nil { t.Fatal(err) } - startOptions := types.ContainerStartOptions{} - err = cli.ContainerStart(context.Background(), ctr.ID, startOptions) + startOptions := ce.ContainerStartOptions{} + err = cli.ContainerStart(ID, startOptions) if err != nil { t.Fatal(err) } - defer cleanContainerQuiet(t, cli, ctr.ID) - rc := waitForContainer(t, cli, ctr.ID, 20*time.Second) - out := inspectLogs(t, cli, ctr.ID) + defer cleanContainerQuiet(t, cli, ID) + rc := waitForContainer(t, cli, ID, 20*time.Second) + out := inspectLogs(t, cli, ID) t.Logf("One shot container finished with rc=%v, output=%v", rc, out) return rc, out } -func startMultiVolumeQueueManager(t *testing.T, cli *client.Client, dataVol bool, qmsharedlogs string, qmshareddata string, env []string) (error, string, string) { +func startMultiVolumeQueueManager(t *testing.T, cli ce.ContainerInterface, dataVol bool, qmsharedlogs string, qmshareddata string, env []string) (error, string, string) { id := strconv.FormatInt(time.Now().UnixNano(), 10) - qmdata := createVolume(t, cli, id) - containerConfig := container.Config{ + volume := createVolume(t, cli, id) + containerConfig := ce.ContainerConfig{ Image: imageName(), Env: env, } - var hostConfig container.HostConfig + var hostConfig ce.ContainerHostConfig if !dataVol { - hostConfig = container.HostConfig{} + hostConfig = ce.ContainerHostConfig{} } else if qmsharedlogs == "" && qmshareddata == "" { - hostConfig = getHostConfig(t, 1, "", "", qmdata.Name) + hostConfig = getHostConfig(t, 1, "", "", volume) } else if qmsharedlogs == "" { - hostConfig = getHostConfig(t, 2, "", qmshareddata, qmdata.Name) + hostConfig = getHostConfig(t, 2, "", qmshareddata, volume) } else if qmshareddata == "" { - hostConfig = getHostConfig(t, 3, qmsharedlogs, "", qmdata.Name) + hostConfig = getHostConfig(t, 3, qmsharedlogs, "", volume) } else { - hostConfig = getHostConfig(t, 4, qmsharedlogs, qmshareddata, qmdata.Name) + hostConfig = getHostConfig(t, 4, qmsharedlogs, qmshareddata, volume) } - networkingConfig := network.NetworkingConfig{} - qm, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+id) + networkingConfig := ce.ContainerNetworkSettings{} + qmID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+id) if err != nil { return err, "", "" } - startContainer(t, cli, qm.ID) + startContainer(t, cli, qmID) - return nil, qm.ID, qmdata.Name + return nil, qmID, volume } -func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata string, qmdata string) container.HostConfig { +func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata string, qmdata string) ce.ContainerHostConfig { - var hostConfig container.HostConfig + var hostConfig ce.ContainerHostConfig switch mounts { case 1: - hostConfig = container.HostConfig{ + hostConfig = ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), qmdata + ":/mnt/mqm", }, } case 2: - hostConfig = container.HostConfig{ + hostConfig = ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), qmdata + ":/mnt/mqm", @@ -470,7 +445,7 @@ func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata s }, } case 3: - hostConfig = container.HostConfig{ + hostConfig = ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), qmdata + ":/mnt/mqm", @@ -478,7 +453,7 @@ func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata s }, } case 4: - hostConfig = container.HostConfig{ + hostConfig = ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), qmdata + ":/mnt/mqm", @@ -491,27 +466,28 @@ func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata s return hostConfig } -func startContainer(t *testing.T, cli *client.Client, ID string) { +func startContainer(t *testing.T, cli ce.ContainerInterface, ID string) { t.Logf("Starting container: %v", ID) - startOptions := types.ContainerStartOptions{} - err := cli.ContainerStart(context.Background(), ID, startOptions) + startOptions := ce.ContainerStartOptions{} + err := cli.ContainerStart(ID, startOptions) if err != nil { t.Fatal(err) } } -func stopContainer(t *testing.T, cli *client.Client, ID string) { +func stopContainer(t *testing.T, cli ce.ContainerInterface, ID string) { t.Logf("Stopping container: %v", ID) timeout := 10 * time.Second - err := cli.ContainerStop(context.Background(), ID, &timeout) //Duration(20)*time.Second) + err := cli.ContainerStop(ID, &timeout) //Duration(20)*time.Second) if err != nil { - t.Fatal(err) + // Just log the error and continue + t.Log(err) } } -func killContainer(t *testing.T, cli *client.Client, ID string, signal string) { +func killContainer(t *testing.T, cli ce.ContainerInterface, ID string, signal string) { t.Logf("Killing container: %v", ID) - err := cli.ContainerKill(context.Background(), ID, signal) + err := cli.ContainerKill(ID, signal) if err != nil { t.Fatal(err) } @@ -545,17 +521,17 @@ func getCoverageExitCode(t *testing.T, orig int64) int64 { } // waitForContainer waits until a container has exited -func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout time.Duration) int64 { +func waitForContainer(t *testing.T, cli ce.ContainerInterface, ID string, timeout time.Duration) int64 { c, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() t.Logf("Waiting for container for %s", timeout) - okC, errC := cli.ContainerWait(c, ID, container.WaitConditionNotRunning) + okC, errC := cli.ContainerWait(c, ID, ce.ContainerStateNotRunning) var rc int64 select { case err := <-errC: t.Fatal(err) case ok := <-okC: - rc = ok.StatusCode + rc = ok } if coverage() { // COVERAGE: When running coverage, the exit code is written to a file, @@ -567,76 +543,13 @@ func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout time. } // execContainer runs a command in a running container, and returns the exit code and output -func execContainer(t *testing.T, cli *client.Client, ID string, user string, cmd []string) (int, string) { +func execContainer(t *testing.T, cli ce.ContainerInterface, ID string, user string, cmd []string) (int, string) { t.Logf("Running command: %v", cmd) - config := types.ExecConfig{ - User: user, - Privileged: false, - Tty: false, - AttachStdin: false, - // Note that you still need to attach stdout/stderr, even though they're not wanted - AttachStdout: true, - AttachStderr: true, - Detach: false, - Cmd: cmd, - } - resp, err := cli.ContainerExecCreate(context.Background(), ID, config) - if err != nil { - t.Fatal(err) - } - hijack, err := cli.ContainerExecAttach(context.Background(), resp.ID, types.ExecStartCheck{}) - if err != nil { - t.Fatal(err) - } - defer hijack.Close() - time.Sleep(time.Millisecond * 10) - err = cli.ContainerExecStart(context.Background(), resp.ID, types.ExecStartCheck{ - Detach: false, - Tty: false, - }) - if err != nil { - t.Fatal(err) - } - // Wait for the command to finish - var exitcode int - var outputStr string - for { - inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID) - if err != nil { - t.Fatal(err) - } - if inspect.Running { - continue - } - - exitcode = inspect.ExitCode - buf := new(bytes.Buffer) - // Each output line has a header, which needs to be removed - _, err = stdcopy.StdCopy(buf, buf, hijack.Reader) - if err != nil { - t.Fatal(err) - } - - outputStr = strings.TrimSpace(buf.String()) - - /* Commented out on 14/06/2018 as it might not be needed after adding - * pause between ContainerExecAttach and ContainerExecStart. - * TODO If intermittent failures do not occur, remove and refactor. - * - * // Before we go let's just double check it did actually finish running - * // because sometimes we get a "Exec command already running error" - * alreadyRunningErr := regexp.MustCompile("Error: Exec command .* is already running") - * if alreadyRunningErr.MatchString(outputStr) { - * continue - * } - */ - break - } - + exitcode, outputStr := cli.ExecContainer(ID, user, cmd) return exitcode, outputStr } -func waitForReady(t *testing.T, cli *client.Client, ID string) { +func waitForReady(t *testing.T, cli ce.ContainerInterface, ID string) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() @@ -662,57 +575,47 @@ func waitForReady(t *testing.T, cli *client.Client, ID string) { } } -func getIPAddress(t *testing.T, cli *client.Client, ID string) string { - ctr, err := cli.ContainerInspect(context.Background(), ID) - if err != nil { - t.Fatal(err) - } - return ctr.NetworkSettings.IPAddress -} - -func createNetwork(t *testing.T, cli *client.Client) string { +func createNetwork(t *testing.T, cli ce.ContainerInterface) string { name := "test" t.Logf("Creating network: %v", name) - opts := types.NetworkCreate{} - net, err := cli.NetworkCreate(context.Background(), name, opts) + opts := ce.NetworkCreateOptions{} + netID, err := cli.NetworkCreate(name, opts) if err != nil { t.Fatal(err) } - t.Logf("Created network %v with ID %v", name, net.ID) - return net.ID + t.Logf("Created network %v with ID %v", name, netID) + return netID } -func removeNetwork(t *testing.T, cli *client.Client, ID string) { +func removeNetwork(t *testing.T, cli ce.ContainerInterface, ID string) { t.Logf("Removing network ID: %v", ID) - err := cli.NetworkRemove(context.Background(), ID) + err := cli.NetworkRemove(ID) if err != nil { t.Fatal(err) } } -func createVolume(t *testing.T, cli *client.Client, name string) types.Volume { - v, err := cli.VolumeCreate(context.Background(), volume.VolumeCreateBody{ - Driver: "local", - DriverOpts: map[string]string{}, - Labels: map[string]string{}, - Name: name, +func createVolume(t *testing.T, cli ce.ContainerInterface, name string) string { + v, err := cli.VolumeCreate(ce.VolumeCreateOptions{ + Driver: "local", + Name: name, }) if err != nil { t.Fatal(err) } - t.Logf("Created volume %v", v.Name) + t.Logf("Created volume %v", v) return v } -func removeVolume(t *testing.T, cli *client.Client, name string) { +func removeVolume(t *testing.T, cli ce.ContainerInterface, name string) { t.Logf("Removing volume %v", name) - err := cli.VolumeRemove(context.Background(), name, true) + err := cli.VolumeRemove(name, true) if err != nil { t.Fatal(err) } } -func inspectTextLogs(t *testing.T, cli *client.Client, ID string) string { +func inspectTextLogs(t *testing.T, cli ce.ContainerInterface, ID string) string { jsonLogs := inspectLogs(t, cli, ID) scanner := bufio.NewScanner(strings.NewReader(jsonLogs)) b := make([]byte, 64*1024) @@ -734,24 +637,14 @@ func inspectTextLogs(t *testing.T, cli *client.Client, ID string) string { return buf.String() } -func inspectLogs(t *testing.T, cli *client.Client, ID string) string { +func inspectLogs(t *testing.T, cli ce.ContainerInterface, ID string) string { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - reader, err := cli.ContainerLogs(ctx, ID, types.ContainerLogsOptions{ - ShowStdout: true, - ShowStderr: true, - }) + logs, err := cli.GetContainerLogs(ctx, ID, ce.ContainerLogsOptions{}) if err != nil { t.Fatal(err) } - buf := new(bytes.Buffer) - - // Each output line has a header, which needs to be removed - _, err = stdcopy.StdCopy(buf, buf, reader) - if err != nil { - t.Fatal(err) - } - return buf.String() + return logs } // generateTAR creates a TAR-formatted []byte, with the specified files included. @@ -781,76 +674,54 @@ func generateTAR(t *testing.T, files []struct{ Name, Body string }) []byte { } // createImage creates a new Docker image with the specified files included. -func createImage(t *testing.T, cli *client.Client, files []struct{ Name, Body string }) string { +func createImage(t *testing.T, cli ce.ContainerInterface, files []struct{ Name, Body string }) string { r := bytes.NewReader(generateTAR(t, files)) tag := strings.ToLower(t.Name()) - buildOptions := types.ImageBuildOptions{ - Context: r, - Tags: []string{tag}, - } - resp, err := cli.ImageBuild(context.Background(), r, buildOptions) + + tmpDir, err := os.MkdirTemp("", "tmp") if err != nil { t.Fatal(err) } - // resp (ImageBuildResponse) contains a series of JSON messages - dec := json.NewDecoder(resp.Body) - for { - m := jsonmessage.JSONMessage{} - err := dec.Decode(&m) - if m.Error != nil { - t.Fatal(m.ErrorMessage) - } - t.Log(strings.TrimSpace(m.Stream)) - if err == io.EOF { - break - } + + defer os.RemoveAll(tmpDir) + + //Write files to temp directory + for _, file := range files { + //Add tag to file name to allow parallel testing + f, err := os.Create(filepath.Join(tmpDir, file.Name)) if err != nil { t.Fatal(err) } + defer f.Close() + + body := []byte(file.Body) + _, err = f.Write(body) + if err != nil { + t.Fatal(err) + } + } + _, err = cli.ImageBuild(r, tag, filepath.Join(tmpDir, files[0].Name)) + if err != nil { + t.Fatal(err) } return tag } // deleteImage deletes a Docker image -func deleteImage(t *testing.T, cli *client.Client, id string) { - cli.ImageRemove(context.Background(), id, types.ImageRemoveOptions{ +func deleteImage(t *testing.T, cli ce.ContainerInterface, id string) { + cli.ImageRemove(id, ce.ImageRemoveOptions{ Force: true, }) } -func copyFromContainer(t *testing.T, cli *client.Client, id string, file string) []byte { - reader, _, err := cli.CopyFromContainer(context.Background(), id, file) - if err != nil { - t.Fatal(err) - } - defer reader.Close() - b, err := ioutil.ReadAll(reader) +func copyFromContainer(t *testing.T, cli ce.ContainerInterface, id string, file string) []byte { + b, err := cli.CopyFromContainer(id, file) if err != nil { t.Fatal(err) } return b } -func getPort(t *testing.T, cli *client.Client, ID string, port int) string { - var inspectInfo types.ContainerJSON - var err error - for attemptsRemaining := 3; attemptsRemaining > 0; attemptsRemaining-- { - inspectInfo, err = cli.ContainerInspect(context.Background(), ID) - if err != nil { - t.Fatal(err) - } - portNat := nat.Port(fmt.Sprintf("%d/tcp", port)) - if inspectInfo.NetworkSettings.Ports[portNat] == nil || len(inspectInfo.NetworkSettings.Ports[portNat]) == 0 { - t.Log("Container port not yet bound") - time.Sleep(1 * time.Second) - continue - } - return inspectInfo.NetworkSettings.Ports[portNat][0].HostPort - } - t.Fatal("Failed to get port") - return "" -} - func countLines(t *testing.T, r io.Reader) int { scanner := bufio.NewScanner(r) count := 0 @@ -882,15 +753,6 @@ func countTarLines(t *testing.T, b []byte) int { return total } -func getMQVersion(t *testing.T, cli *client.Client) (string, error) { - inspect, _, err := cli.ImageInspectWithRaw(context.Background(), imageName()) - if err != nil { - return "", err - } - version := inspect.ContainerConfig.Labels["version"] - return version, nil -} - // scanForExcludedEntries scans for default excluded messages func scanForExcludedEntries(msg string) bool { if strings.Contains(msg, "AMQ5041I") || strings.Contains(msg, "AMQ5052I") || @@ -917,7 +779,7 @@ func checkLogForValidJSON(jsonLogs string) bool { // runContainerWithAllConfig creates and starts a container, using the supplied ContainerConfig, HostConfig, // NetworkingConfig, and container name (or the value of t.Name if containerName=""). -func runContainerWithAllConfigError(t *testing.T, cli *client.Client, containerConfig *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (string, error) { +func runContainerWithAllConfigError(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig, networkingConfig *ce.ContainerNetworkSettings, containerName string) (string, error) { if containerName == "" { containerName = t.Name() } @@ -932,21 +794,21 @@ func runContainerWithAllConfigError(t *testing.T, cli *client.Client, containerC containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov") containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t)) t.Logf("Running container (%s)", containerConfig.Image) - ctr, err := cli.ContainerCreate(context.Background(), containerConfig, hostConfig, networkingConfig, containerName) + ID, err := cli.ContainerCreate(containerConfig, hostConfig, networkingConfig, containerName) if err != nil { return "", err } - err = startContainerError(t, cli, ctr.ID) + err = startContainerError(t, cli, ID) if err != nil { return "", err } - return ctr.ID, nil + return ID, nil } -func startContainerError(t *testing.T, cli *client.Client, ID string) error { +func startContainerError(t *testing.T, cli ce.ContainerInterface, ID string) error { t.Logf("Starting container: %v", ID) - startOptions := types.ContainerStartOptions{} - err := cli.ContainerStart(context.Background(), ID, startOptions) + startOptions := ce.ContainerStartOptions{} + err := cli.ContainerStart(ID, startOptions) if err != nil { return err } @@ -955,7 +817,7 @@ func startContainerError(t *testing.T, cli *client.Client, ID string) error { } // testLogFilePages validates that the specified number of logFilePages is present in the qm.ini file. -func testLogFilePages(t *testing.T, cli *client.Client, id string, qmName string, expectedLogFilePages string) { +func testLogFilePages(t *testing.T, cli ce.ContainerInterface, id string, qmName string, expectedLogFilePages string) { catIniFileCommand := fmt.Sprintf("cat /var/mqm/qmgrs/" + qmName + "/qm.ini") _, iniContent := execContainer(t, cli, id, "", []string{"bash", "-c", catIniFileCommand}) @@ -964,8 +826,8 @@ func testLogFilePages(t *testing.T, cli *client.Client, id string, qmName string } } -//waitForMessageInLog will check for a particular message with wait -func waitForMessageInLog(t *testing.T, cli *client.Client, id string, expecteMessageId string) (string, error) { +// waitForMessageInLog will check for a particular message with wait +func waitForMessageInLog(t *testing.T, cli ce.ContainerInterface, id string, expecteMessageId string) (string, error) { var jsonLogs string ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() diff --git a/test/container/go.mod b/test/container/go.mod new file mode 100644 index 0000000..c307de0 --- /dev/null +++ b/test/container/go.mod @@ -0,0 +1,3 @@ +module github.com/ibm-messaging/mq-container/test/container + +go 1.18 diff --git a/test/container/go.sum b/test/container/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/test/docker/main.go b/test/container/main.go similarity index 100% rename from test/docker/main.go rename to test/container/main.go diff --git a/test/docker/mq_multi_instance_test.go b/test/container/mq_multi_instance_test.go similarity index 83% rename from test/docker/mq_multi_instance_test.go rename to test/container/mq_multi_instance_test.go index cdbcd10..b7ae35e 100644 --- a/test/docker/mq_multi_instance_test.go +++ b/test/container/mq_multi_instance_test.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2019, 2022 +© Copyright IBM Corporation 2019, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import ( "testing" "time" - "github.com/docker/docker/client" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) var miEnv = []string{ @@ -34,10 +34,7 @@ var miEnv = []string{ // and starts/stop them checking we always have an active and standby func TestMultiInstanceStartStop(t *testing.T) { t.Skipf("Skipping %v until test defect fixed", t.Name()) - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli) if err != nil { t.Fatal(err) @@ -76,10 +73,7 @@ func TestMultiInstanceStartStop(t *testing.T) { // TestMultiInstanceContainerStop starts 2 containers in a multi instance queue manager configuration, // stops the active queue manager, then checks to ensure the backup queue manager becomes active func TestMultiInstanceContainerStop(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli) if err != nil { t.Fatal(err) @@ -122,21 +116,16 @@ func TestMultiInstanceContainerStop(t *testing.T) { // configuration, then checks to ensure that both an active and standby queue manager have been started func TestMultiInstanceRace(t *testing.T) { t.Skipf("Skipping %v until file lock is implemented", t.Name()) - - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - + cli := ce.NewContainerClient() qmsharedlogs := createVolume(t, cli, "qmsharedlogs") - defer removeVolume(t, cli, qmsharedlogs.Name) + defer removeVolume(t, cli, qmsharedlogs) qmshareddata := createVolume(t, cli, "qmshareddata") - defer removeVolume(t, cli, qmshareddata.Name) + defer removeVolume(t, cli, qmshareddata) qmsChannel := make(chan QMChan) - go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel) - go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel) + go singleMultiInstanceQueueManager(t, cli, qmsharedlogs, qmshareddata, qmsChannel) + go singleMultiInstanceQueueManager(t, cli, qmsharedlogs, qmshareddata, qmsChannel) qm1a := <-qmsChannel if qm1a.Error != nil { @@ -159,7 +148,7 @@ func TestMultiInstanceRace(t *testing.T) { waitForReady(t, cli, qm1aId) waitForReady(t, cli, qm1bId) - err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId) + err, _, _ := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId) if err != nil { t.Fatal(err) } @@ -169,10 +158,7 @@ func TestMultiInstanceRace(t *testing.T) { // mounts, then checks to ensure that the container terminates with the expected message func TestMultiInstanceNoSharedMounts(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", "", miEnv) if err != nil { @@ -188,15 +174,12 @@ func TestMultiInstanceNoSharedMounts(t *testing.T) { // TestMultiInstanceNoSharedLogs starts 2 multi instance queue managers without providing a shared log // mount, then checks to ensure that the container terminates with the expected message func TestMultiInstanceNoSharedLogs(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() qmshareddata := createVolume(t, cli, "qmshareddata") - defer removeVolume(t, cli, qmshareddata.Name) + defer removeVolume(t, cli, qmshareddata) - err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, miEnv) + err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata, miEnv) if err != nil { t.Fatal(err) } @@ -210,15 +193,12 @@ func TestMultiInstanceNoSharedLogs(t *testing.T) { // TestMultiInstanceNoSharedData starts 2 multi instance queue managers without providing a shared data // mount, then checks to ensure that the container terminates with the expected message func TestMultiInstanceNoSharedData(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() qmsharedlogs := createVolume(t, cli, "qmsharedlogs") - defer removeVolume(t, cli, qmsharedlogs.Name) + defer removeVolume(t, cli, qmsharedlogs) - err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", miEnv) + err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, "", miEnv) if err != nil { t.Fatal(err) } @@ -232,10 +212,7 @@ func TestMultiInstanceNoSharedData(t *testing.T) { // TestMultiInstanceNoMounts starts 2 multi instance queue managers without providing a shared data // mount, then checks to ensure that the container terminates with the expected message func TestMultiInstanceNoMounts(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, false, "", "", miEnv) if err != nil { diff --git a/test/docker/mq_multi_instance_test_util.go b/test/container/mq_multi_instance_test_util.go similarity index 72% rename from test/docker/mq_multi_instance_test_util.go rename to test/container/mq_multi_instance_test_util.go index 9a64be9..24627af 100644 --- a/test/docker/mq_multi_instance_test_util.go +++ b/test/container/mq_multi_instance_test_util.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2019, 2022 +© Copyright IBM Corporation 2019, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import ( "testing" "time" - "github.com/docker/docker/client" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) type QMChan struct { @@ -34,27 +34,27 @@ type QMChan struct { // configureMultiInstance creates the volumes and containers required for basic testing // of multi instance queue managers. Returns error, qm1a ID, qm1b ID, slice of volume names -func configureMultiInstance(t *testing.T, cli *client.Client) (error, string, string, []string) { +func configureMultiInstance(t *testing.T, cli ce.ContainerInterface) (error, string, string, []string) { qmsharedlogs := createVolume(t, cli, "qmsharedlogs") qmshareddata := createVolume(t, cli, "qmshareddata") - err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv) + err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv) if err != nil { return err, "", "", []string{} } time.Sleep(10 * time.Second) - err, qm1bId, qm1bData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv) + err, qm1bId, qm1bData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv) if err != nil { return err, "", "", []string{} } - volumes := []string{qmsharedlogs.Name, qmshareddata.Name, qm1aData, qm1bData} + volumes := []string{qmsharedlogs, qmshareddata, qm1aData, qm1bData} return nil, qm1aId, qm1bId, volumes } -func singleMultiInstanceQueueManager(t *testing.T, cli *client.Client, qmsharedlogs string, qmshareddata string, qmsChannel chan QMChan) { +func singleMultiInstanceQueueManager(t *testing.T, cli ce.ContainerInterface, qmsharedlogs string, qmshareddata string, qmsChannel chan QMChan) { err, qmId, qmData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv) if err != nil { qmsChannel <- QMChan{Error: err} @@ -62,7 +62,7 @@ func singleMultiInstanceQueueManager(t *testing.T, cli *client.Client, qmsharedl qmsChannel <- QMChan{QMId: qmId, QMData: qmData} } -func getActiveStandbyQueueManager(t *testing.T, cli *client.Client, qm1aId string, qm1bId string) (error, string, string) { +func getActiveStandbyQueueManager(t *testing.T, cli ce.ContainerInterface, qm1aId string, qm1bId string) (error, string, string) { qm1aStatus := getQueueManagerStatus(t, cli, qm1aId, "QM1") qm1bStatus := getQueueManagerStatus(t, cli, qm1bId, "QM1") @@ -75,7 +75,7 @@ func getActiveStandbyQueueManager(t *testing.T, cli *client.Client, qm1aId strin return err, "", "" } -func getQueueManagerStatus(t *testing.T, cli *client.Client, containerID string, queueManagerName string) string { +func getQueueManagerStatus(t *testing.T, cli ce.ContainerInterface, containerID string, queueManagerName string) string { _, dspmqOut := execContainer(t, cli, containerID, "", []string{"bash", "-c", "dspmq", "-m", queueManagerName}) t.Logf("dspmq for %v (%v) returned: %v", containerID, queueManagerName, dspmqOut) regex := regexp.MustCompile(`STATUS\(.*\)`) @@ -84,7 +84,7 @@ func getQueueManagerStatus(t *testing.T, cli *client.Client, containerID string, return status } -func waitForTerminationMessage(t *testing.T, cli *client.Client, qmId string, terminationString string, timeout time.Duration) { +func waitForTerminationMessage(t *testing.T, cli ce.ContainerInterface, qmId string, terminationString string, timeout time.Duration) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() for { @@ -93,7 +93,7 @@ func waitForTerminationMessage(t *testing.T, cli *client.Client, qmId string, te m := terminationMessage(t, cli, qmId) if m != "" { if !strings.Contains(m, terminationString) { - t.Fatalf("Expected container to fail on missing required mount. Got termination message: %v", m) + t.Fatalf("Expected container to fail with termination message %v. Got termination message: %v", terminationString, m) } return } diff --git a/test/docker/mq_native_ha_test.go b/test/container/mq_native_ha_test.go similarity index 67% rename from test/docker/mq_native_ha_test.go rename to test/container/mq_native_ha_test.go index dc88756..f533249 100644 --- a/test/docker/mq_native_ha_test.go +++ b/test/container/mq_native_ha_test.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2021, 2022 +© Copyright IBM Corporation 2021, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,20 +16,19 @@ limitations under the License. package main import ( - "github.com/docker/docker/client" "strings" "testing" + "time" + + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) // TestNativeHABasic creates 3 containers in a Native HA queue manager configuration // and ensures the queue manger and replicas start as expected func TestNativeHABasic(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } @@ -40,21 +39,18 @@ func TestNativeHABasic(t *testing.T) { containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} qmVolumes := []string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i vol := createVolume(t, cli, containerNames[i]) - defer removeVolume(t, cli, vol.Name) - qmVolumes = append(qmVolumes, vol.Name) - - containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) - hostConfig := getHostConfig(t, 1, "", "", vol.Name) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - + defer removeVolume(t, cli, vol) + qmVolumes = append(qmVolumes, vol) + containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, basePort) + hostConfig := getHostConfig(t, 1, "", "", vol) + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) qmReplicaIDs[i] = ctr @@ -74,12 +70,9 @@ func TestNativeHABasic(t *testing.T) { // queue manager comes back as a replica func TestNativeHAFailover(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } @@ -90,21 +83,18 @@ func TestNativeHAFailover(t *testing.T) { containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} qmVolumes := []string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i vol := createVolume(t, cli, containerNames[i]) - defer removeVolume(t, cli, vol.Name) - qmVolumes = append(qmVolumes, vol.Name) - - containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) - hostConfig := getHostConfig(t, 1, "", "", vol.Name) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - + defer removeVolume(t, cli, vol) + qmVolumes = append(qmVolumes, vol) + containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, basePort) + hostConfig := getHostConfig(t, 1, "", "", vol) + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) qmReplicaIDs[i] = ctr @@ -132,33 +122,31 @@ func TestNativeHAFailover(t *testing.T) { // TestNativeHASecure creates 3 containers in a Native HA queue manager configuration // with HA TLS enabled, and ensures the queue manger and replicas start as expected func TestNativeHASecure(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } if version < "9.2.2.0" { t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version) } + if isARM(t) { + t.Skip("Skipping as an issue has been identified for the arm64 MQ image") + } containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true") hostConfig := getNativeHASecureHostConfig(t) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) qmReplicaIDs[i] = ctr @@ -177,12 +165,9 @@ func TestNativeHASecure(t *testing.T) { // with HA TLS enabled, overrides the default CipherSpec, and ensures the queue manger // and replicas start as expected func TestNativeHASecureCipherSpec(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } @@ -192,18 +177,16 @@ func TestNativeHASecureCipherSpec(t *testing.T) { containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=TLS_AES_256_GCM_SHA384") hostConfig := getNativeHASecureHostConfig(t) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) qmReplicaIDs[i] = ctr @@ -222,12 +205,9 @@ func TestNativeHASecureCipherSpec(t *testing.T) { // with HA TLS FIPS enabled, overrides the default CipherSpec, and ensures the queue manger // and replicas start as expected. This test uses FIPS compliant cipher. func TestNativeHASecureCipherSpecFIPS(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } @@ -237,19 +217,17 @@ func TestNativeHASecureCipherSpecFIPS(t *testing.T) { containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) // MQ_NATIVE_HA_CIPHERSPEC is set a FIPS compliant cipherspec. containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=TLS_RSA_WITH_AES_128_GCM_SHA256", "MQ_ENABLE_FIPS=true") hostConfig := getNativeHASecureHostConfig(t) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) qmReplicaIDs[i] = ctr @@ -272,12 +250,9 @@ func TestNativeHASecureCipherSpecFIPS(t *testing.T) { // with HA TLS FIPS enabled with non-FIPS cipher, overrides the default CipherSpec, and // ensures the queue manger and replicas don't start as expected func TestNativeHASecureCipherSpecNonFIPSCipher(t *testing.T) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() - version, err := getMQVersion(t, cli) + version, err := cli.GetMQVersion(imageName()) if err != nil { t.Fatal(err) } @@ -287,26 +262,24 @@ func TestNativeHASecureCipherSpecNonFIPSCipher(t *testing.T) { containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"} qmReplicaIDs := [3]string{} - qmNetwork, err := createBridgeNetwork(cli, t) - if err != nil { - t.Fatal(err) - } - defer removeBridgeNetwork(cli, qmNetwork.ID) - + //Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort + //If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50 + basePort := 14551 for i := 0; i <= 2; i++ { + nhaPort := basePort + i containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort) // MQ_NATIVE_HA_CIPHERSPEC is set a FIPS non-compliant cipherspec - SSL_ECDHE_ECDSA_WITH_RC4_128_SHA - containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=TLS_RSA_WITH_AES_128_GCM_SHA256", "MQ_ENABLE_FIPS=true") + containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=SSL_ECDHE_ECDSA_WITH_RC4_128_SHA", "MQ_ENABLE_FIPS=true") hostConfig := getNativeHASecureHostConfig(t) - networkingConfig := getNativeHANetworkConfig(qmNetwork.ID) - - ctr, err := runContainerWithAllConfigError(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) + hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig) + networkingConfig := getNativeHANetworkConfig("host") + ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i]) defer cleanContainer(t, cli, ctr) // We expect container to fail in this case because the cipher is non-FIPS and we have asked for FIPS compliance // by setting MQ_ENABLE_FIPS=true - if err == nil { - t.Logf("Container start expected to fail but did not. %v", err) - } qmReplicaIDs[i] = ctr } + for i := 0; i <= 2; i++ { + waitForTerminationMessage(t, cli, qmReplicaIDs[i], "/opt/mqm/bin/strmqm: exit status 23", 60*time.Second) + } } diff --git a/test/docker/mq_native_ha_test_util.go b/test/container/mq_native_ha_test_util.go similarity index 65% rename from test/docker/mq_native_ha_test_util.go rename to test/container/mq_native_ha_test_util.go index aa24054..2219e62 100644 --- a/test/docker/mq_native_ha_test_util.go +++ b/test/container/mq_native_ha_test_util.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2021 +© Copyright IBM Corporation 2021, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,13 +19,11 @@ import ( "context" "fmt" "path/filepath" + "strconv" "testing" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) const defaultHAPort = 9414 @@ -36,16 +34,8 @@ type HAReplicaStatus struct { Replica [2]string } -func createBridgeNetwork(cli *client.Client, t *testing.T) (types.NetworkCreateResponse, error) { - return cli.NetworkCreate(context.Background(), t.Name(), types.NetworkCreate{}) -} - -func removeBridgeNetwork(cli *client.Client, networkID string) error { - return cli.NetworkRemove(context.Background(), networkID) -} - -func getNativeHAContainerConfig(containerName string, replicaNames [3]string, haPort int) container.Config { - return container.Config{ +func getNativeHAContainerConfig(containerName string, replicaNames [3]string, haPort int) ce.ContainerConfig { + return ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=QM1", @@ -55,15 +45,18 @@ func getNativeHAContainerConfig(containerName string, replicaNames [3]string, ha fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_NAME=%s", replicaNames[0]), fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_NAME=%s", replicaNames[1]), fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_NAME=%s", replicaNames[2]), - fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_REPLICATION_ADDRESS=%s(%d)", replicaNames[0], haPort), - fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_REPLICATION_ADDRESS=%s(%d)", replicaNames[1], haPort), - fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_REPLICATION_ADDRESS=%s(%d)", replicaNames[2], haPort), + fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+0), + fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+1), + fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+2), }, + //When using the host for networking a consistent user was required. If a random user is used then the following example error was recorded. + //AMQ3209E: Native HA connection rejected due to configuration mismatch of 'QmgrUserId=5024' + User: "1111", } } -func getNativeHASecureHostConfig(t *testing.T) container.HostConfig { - return container.HostConfig{ +func getNativeHASecureHostConfig(t *testing.T) ce.ContainerHostConfig { + return ce.ContainerHostConfig{ Binds: []string{ coverageBind(t), filepath.Join(getCwd(t, true), "../tls") + ":/etc/mqm/ha/pki/keys/ha", @@ -71,15 +64,30 @@ func getNativeHASecureHostConfig(t *testing.T) container.HostConfig { } } -func getNativeHANetworkConfig(networkID string) network.NetworkingConfig { - return network.NetworkingConfig{ - EndpointsConfig: map[string]*network.EndpointSettings{ - networkID: &network.EndpointSettings{}, - }, +func getNativeHANetworkConfig(networkID string) ce.ContainerNetworkSettings { + return ce.ContainerNetworkSettings{ + Networks: []string{networkID}, } } -func getActiveReplicaInstances(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) (HAReplicaStatus, error) { +// populatePortBindings writes port bindings to the host config +func populateNativeHAPortBindings(ports []int, nativeHaPort int, hostConfig ce.ContainerHostConfig) ce.ContainerHostConfig { + hostConfig.PortBindings = []ce.PortBinding{} + var binding ce.PortBinding + for i, p := range ports { + port := fmt.Sprintf("%v/tcp", p) + binding = ce.PortBinding{ + ContainerPort: port, + HostIP: "0.0.0.0", + //Offset the ports by 50 if there are multiple + HostPort: strconv.Itoa(nativeHaPort + 50*i), + } + hostConfig.PortBindings = append(hostConfig.PortBindings, binding) + } + return hostConfig +} + +func getActiveReplicaInstances(t *testing.T, cli ce.ContainerInterface, qmReplicaIDs [3]string) (HAReplicaStatus, error) { var actives []string var replicas []string @@ -104,7 +112,7 @@ func getActiveReplicaInstances(t *testing.T, cli *client.Client, qmReplicaIDs [3 return HAReplicaStatus{actives[0], [2]string{replicas[0], replicas[1]}}, nil } -func waitForReadyHA(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) { +func waitForReadyHA(t *testing.T, cli ce.ContainerInterface, qmReplicaIDs [3]string) { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) defer cancel() @@ -129,7 +137,7 @@ func waitForReadyHA(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) { } } -func waitForFailoverHA(t *testing.T, cli *client.Client, replicas [2]string) { +func waitForFailoverHA(t *testing.T, cli ce.ContainerInterface, replicas [2]string) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() diff --git a/test/docker/mqmetric_test.go b/test/container/mqmetric_test.go similarity index 90% rename from test/docker/mqmetric_test.go rename to test/container/mqmetric_test.go index 891f711..79b1647 100644 --- a/test/docker/mqmetric_test.go +++ b/test/container/mqmetric_test.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2018, 2022 +© Copyright IBM Corporation 2018, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,20 +22,20 @@ import ( "testing" "time" - "github.com/docker/docker/client" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) func TestGoldenPathMetric(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) + port, err := cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -55,14 +55,14 @@ func TestGoldenPathMetric(t *testing.T) { func TestMetricNames(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) + port, err := cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -99,14 +99,14 @@ func TestMetricNames(t *testing.T) { func TestMetricLabels(t *testing.T) { t.Parallel() + cli := ce.NewContainerClient() requiredLabels := []string{"qmgr"} - cli, err := client.NewClientWithOpts(client.FromEnv) + id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) + defer cleanContainer(t, cli, id) + port, err := cli.GetContainerPort(id, defaultMetricPort) if err != nil { t.Fatal(err) } - id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) - defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -148,13 +148,13 @@ func TestMetricLabels(t *testing.T) { func TestRapidFirePrometheus(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) + defer cleanContainer(t, cli, id) + port, err := cli.GetContainerPort(id, defaultMetricPort) if err != nil { t.Fatal(err) } - id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) - defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -182,13 +182,13 @@ func TestRapidFirePrometheus(t *testing.T) { func TestSlowPrometheus(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) + defer cleanContainer(t, cli, id) + port, err := cli.GetContainerPort(id, defaultMetricPort) if err != nil { t.Fatal(err) } - id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) - defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -213,15 +213,14 @@ func TestSlowPrometheus(t *testing.T) { func TestContainerRestart(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) + cli := ce.NewContainerClient() + id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) + defer cleanContainer(t, cli, id) + port, err := cli.GetContainerPort(id, defaultMetricPort) if err != nil { t.Fatal(err) } - id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) - defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) - // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -239,7 +238,10 @@ func TestContainerRestart(t *testing.T) { stopContainer(t, cli, id) // Start the container cleanly startContainer(t, cli, id) - port = getPort(t, cli, id, defaultMetricPort) + port, err = cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -261,15 +263,14 @@ func TestContainerRestart(t *testing.T) { func TestQMRestart(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } - + cli := ce.NewContainerClient() id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) defer cleanContainer(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) + port, err := cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -319,14 +320,14 @@ func TestQMRestart(t *testing.T) { func TestValidValues(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort}) defer cleanContainer(t, cli, id) // hostname := getIPAddress(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) + port, err := cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -343,7 +344,7 @@ func TestValidValues(t *testing.T) { // Check that the values for each metric are valid numbers // can be either int, float or exponential - all these can be parsed by ParseFloat function for _, e := range metrics { - if _, err = strconv.ParseFloat(e.Value, 64); err != nil { + if _, err := strconv.ParseFloat(e.Value, 64); err != nil { t.Errorf("Value (%s) for key (%s) is not a valid number", e.Value, e.Key) } } @@ -355,14 +356,14 @@ func TestValidValues(t *testing.T) { func TestChangingValues(t *testing.T) { t.Parallel() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - t.Fatal(err) - } + cli := ce.NewContainerClient() id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{1414, defaultMetricPort}) defer cleanContainer(t, cli, id) // hostname := getIPAddress(t, cli, id) - port := getPort(t, cli, id, defaultMetricPort) + port, err := cli.GetContainerPort(id, defaultMetricPort) + if err != nil { + t.Fatal(err) + } // Now the container is ready we prod the prometheus endpoint until it's up. waitForMetricReady(t, port) @@ -386,7 +387,11 @@ func TestChangingValues(t *testing.T) { } // Send invalid data to the MQ listener to generate a FDC - listener := fmt.Sprintf("localhost:%s", getPort(t, cli, id, 1414)) + noport, err := cli.GetContainerPort(id, 1414) + if err != nil { + t.Fatal(err) + } + listener := fmt.Sprintf("localhost:%s", noport) conn, err := net.Dial("tcp", listener) if err != nil { t.Fatalf("Could not connect to the listener - %v", err) diff --git a/test/docker/mqmetric_test_util.go b/test/container/mqmetric_test_util.go similarity index 97% rename from test/docker/mqmetric_test_util.go rename to test/container/mqmetric_test_util.go index 013269f..5eaea0a 100644 --- a/test/docker/mqmetric_test_util.go +++ b/test/container/mqmetric_test_util.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2018 +© Copyright IBM Corporation 2018, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" + ce "github.com/ibm-messaging/mq-container/test/container/containerengine" ) type mqmetric struct { @@ -146,8 +146,8 @@ func waitForMetricReady(t *testing.T, port string) { t.Fatalf("Metric endpoint failed to startup in timely manner") } -func metricsContainerConfig() *container.Config { - return &container.Config{ +func metricsContainerConfig() *ce.ContainerConfig { + return &ce.ContainerConfig{ Env: []string{ "LICENSE=accept", "MQ_QMGR_NAME=" + defaultMetricQMName, diff --git a/test/docker/go.mod b/test/docker/go.mod deleted file mode 100644 index d6fb653..0000000 --- a/test/docker/go.mod +++ /dev/null @@ -1,40 +0,0 @@ -module github.com/ibm-messaging/mq-container/test/docker - -go 1.18 - -require ( - // Note: This is not actually Docker v17.12! - // Go modules require the use of semver, but Docker does not use semver and has not - // [opted-in to use Go modules](https://github.com/golang/go/wiki/Modules#can-a-module-consume-a-package-that-has-not-opted-in-to-modules) - // This means that when you `go get` Docker, you need to do so based on a commit, - // e.g. `go get -v github.com/docker/docker@420b1d36250f9cfdc561f086f25a213ecb669b6f`, - // which uses the commit for [Docker v19.03.15](https://github.com/moby/moby/releases/tag/v19.03.15) - // Go will then find the latest tag with a semver-compatible tag. In Docker's case, - // v17.12.0 is valid semver, but v18.09 and v19.03 are not. - // Also note: Docker v20.10 is valid semver, but the v20.10 client API requires use of Docker API - // version 1.41 on the server, which is currently too new for the version of Docker in Travis (Ubuntu Bionic) - github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible - github.com/docker/go-connections v0.4.0 -) - -require ( - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/containerd/containerd v1.6.6 // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect - golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect - google.golang.org/grpc v1.46.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gotest.tools v2.2.0+incompatible // indirect -) diff --git a/test/docker/go.sum b/test/docker/go.sum deleted file mode 100644 index f09207b..0000000 --- a/test/docker/go.sum +++ /dev/null @@ -1,199 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= -github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible h1:nhVo1udYfMj0Jsw0lnqrTjjf33aLpdgW9Wve9fHVzhQ= -github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -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= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -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= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test/messaging/src/main/java/com/ibm/mqcontainer/test/JMSTests.java b/test/messaging/src/main/java/com/ibm/mqcontainer/test/JMSTests.java index 24a2001..003a764 100644 --- a/test/messaging/src/main/java/com/ibm/mqcontainer/test/JMSTests.java +++ b/test/messaging/src/main/java/com/ibm/mqcontainer/test/JMSTests.java @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2018, 2022 +© Copyright IBM Corporation 2018, 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ class JMSTests { private static final Logger LOGGER = Logger.getLogger(JMSTests.class.getName()); protected static final String ADDR = System.getenv("MQ_PORT_1414_TCP_ADDR"); protected static final String USER = System.getenv("MQ_USERNAME"); + protected static final String PORT = System.getenv().getOrDefault("MQ_PORT_1414_OVERRIDE", "1414"); protected static final String PASSWORD = System.getenv("MQ_PASSWORD"); protected static final String CHANNEL = System.getenv("MQ_CHANNEL"); protected static final String TRUSTSTORE = System.getenv("MQ_TLS_TRUSTSTORE"); @@ -67,11 +68,11 @@ class JMSTests { return ctx.getSocketFactory(); } - static MQConnectionFactory createMQConnectionFactory(String channel, String addr) throws JMSException, IOException, GeneralSecurityException { + static MQConnectionFactory createMQConnectionFactory(String channel, String addr, String port) throws JMSException, IOException, GeneralSecurityException { MQConnectionFactory factory = new MQConnectionFactory(); factory.setTransportType(WMQConstants.WMQ_CM_CLIENT); factory.setChannel(channel); - factory.setConnectionNameList(String.format("%s(1414)", addr)); + factory.setConnectionNameList(String.format("%s(%s)", addr, port)); if (TRUSTSTORE == null) { LOGGER.info("Not using TLS"); } @@ -83,7 +84,7 @@ class JMSTests { if (ibmjre){ System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "true"); } else { - System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false"); + System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false"); } factory.setSSLCipherSuite(System.getenv("MQ_TLS_CIPHER")); } @@ -93,9 +94,9 @@ class JMSTests { /** * Create a JMSContext with the supplied user and password. */ - static JMSContext create(String channel, String addr, String user, String password) throws JMSException, IOException, GeneralSecurityException { - LOGGER.info(String.format("Connecting to %s/TCP/%s(1414) as %s", channel, addr, user)); - MQConnectionFactory factory = createMQConnectionFactory(channel, addr); + static JMSContext create(String channel, String addr, String port, String user, String password) throws JMSException, IOException, GeneralSecurityException { + LOGGER.info(String.format("Connecting to %s/TCP/%s(%s) as %s", channel, addr, port, user)); + MQConnectionFactory factory = createMQConnectionFactory(channel, addr, port); // If a password is set, make sure it gets sent to the queue manager for authentication if (password != null) { factory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); @@ -107,9 +108,9 @@ class JMSTests { /** * Create a JMSContext with the default user identity (from the OS) */ - static JMSContext create(String channel, String addr) throws JMSException, IOException, GeneralSecurityException { - LOGGER.info(String.format("Connecting to %s/TCP/%s(1414) as OS user '%s'", channel, addr, System.getProperty("user.name"))); - MQConnectionFactory factory = createMQConnectionFactory(channel, addr); + static JMSContext create(String channel, String addr, String port) throws JMSException, IOException, GeneralSecurityException { + LOGGER.info(String.format("Connecting to %s/TCP/%s(%s) as OS user '%s'", channel, addr, port, System.getProperty("user.name"))); + MQConnectionFactory factory = createMQConnectionFactory(channel, addr, port); LOGGER.info(String.format("CSP authentication: %s", factory.getBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP))); return factory.createContext(); } @@ -118,7 +119,7 @@ class JMSTests { private static void waitForQueueManager() { for (int i = 0; i < 20; i++) { try { - Socket s = new Socket(ADDR, 1414); + Socket s = new Socket(ADDR, Integer.parseInt(PORT)); s.close(); return; } catch (IOException e) { @@ -132,7 +133,7 @@ class JMSTests { @Test void putGetTest(TestInfo t) throws Exception { - context = create(CHANNEL, ADDR, USER, PASSWORD); + context = create(CHANNEL, ADDR, PORT, USER, PASSWORD); Queue queue = new MQQueue("DEV.QUEUE.1"); context.createProducer().send(queue, t.getDisplayName()); Message m = context.createConsumer(queue).receive(); @@ -144,7 +145,7 @@ class JMSTests { LOGGER.info(String.format("Password='%s'", PASSWORD)); try { // Don't pass a user/password, which should cause the default identity to be used - context = create(CHANNEL, ADDR); + context = create(CHANNEL, ADDR, PORT); } catch (DetailedJMSSecurityRuntimeException ex) { Throwable cause = ex.getCause(); assertNotNull(cause);