diff --git a/.gitignore b/.gitignore index 1a4ffbb..1f16ae9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store -test/docker/coverage -test/docker/vendor +test/docker-advancedserver/coverage +test/docker-advancedserver/vendor +test/docker-devserver/coverage +test/docker-devserver/vendor test/kubernetes/vendor build coverage diff --git a/Makefile b/Makefile index ab811a1..5393234 100644 --- a/Makefile +++ b/Makefile @@ -67,10 +67,15 @@ build-cov: mkdir -p build cd build; go test -c -covermode=count ../cmd/runmqserver -.PHONY: test -test: build +.PHONY: test-advancedserver +test-advancedserver: build cd pkg/name && go test - cd test/docker && go test + cd test/docker-advancedserver && go test + +.PHONY: test-devserver +test-devserver: build + cd pkg/name && go test + cd test/docker-devserver && go test define docker-build-mq # Create a temporary network to use for the build diff --git a/test/docker/Gopkg.lock b/test/docker-advancedserver/Gopkg.lock similarity index 100% rename from test/docker/Gopkg.lock rename to test/docker-advancedserver/Gopkg.lock diff --git a/test/docker/Gopkg.toml b/test/docker-advancedserver/Gopkg.toml similarity index 100% rename from test/docker/Gopkg.toml rename to test/docker-advancedserver/Gopkg.toml diff --git a/test/docker/docker_api_test.go b/test/docker-advancedserver/docker_api_test.go similarity index 100% rename from test/docker/docker_api_test.go rename to test/docker-advancedserver/docker_api_test.go diff --git a/test/docker/docker_api_test_util.go b/test/docker-advancedserver/docker_api_test_util.go similarity index 100% rename from test/docker/docker_api_test_util.go rename to test/docker-advancedserver/docker_api_test_util.go diff --git a/test/docker/main.go b/test/docker-advancedserver/main.go similarity index 100% rename from test/docker/main.go rename to test/docker-advancedserver/main.go diff --git a/test/docker-devserver/Gopkg.lock b/test/docker-devserver/Gopkg.lock new file mode 100644 index 0000000..6b9fb90 --- /dev/null +++ b/test/docker-devserver/Gopkg.lock @@ -0,0 +1,57 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/Microsoft/go-winio" + packages = ["."] + revision = "78439966b38d69bf38227fbf57ac8a6fee70f69a" + version = "v0.4.5" + +[[projects]] + name = "github.com/docker/distribution" + packages = ["digest","reference"] + revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89" + version = "v2.6.2" + +[[projects]] + name = "github.com/docker/docker" + packages = ["api/types","api/types/blkiodev","api/types/container","api/types/events","api/types/filters","api/types/mount","api/types/network","api/types/reference","api/types/registry","api/types/strslice","api/types/swarm","api/types/time","api/types/versions","api/types/volume","client","pkg/tlsconfig"] + revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" + version = "v1.13.1" + +[[projects]] + name = "github.com/docker/go-connections" + packages = ["nat","sockets","tlsconfig"] + revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" + version = "v0.3.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52" + version = "v0.3.2" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["context","context/ctxhttp","proxy"] + revision = "66aacef3dd8a676686c7ae3716979581e8b03c47" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["windows"] + revision = "9aade4d3a3b7e6d876cd3823ad20ec45fc035402" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "bf6ada0adb63f691f18ca1b3b95f55be8ec360be22928ca9e63ba47846c5687d" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/test/docker-devserver/Gopkg.toml b/test/docker-devserver/Gopkg.toml new file mode 100644 index 0000000..2af502b --- /dev/null +++ b/test/docker-devserver/Gopkg.toml @@ -0,0 +1,20 @@ +# © Copyright IBM Corporation 2017 +# +# 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. + +[[constraint]] + name = "github.com/docker/docker" + version = "^1.12" + +[[constraint]] + name = "github.com/docker/go-connections" \ No newline at end of file diff --git a/test/docker-devserver/docker_api_test.go b/test/docker-devserver/docker_api_test.go new file mode 100644 index 0000000..4e98ea3 --- /dev/null +++ b/test/docker-devserver/docker_api_test.go @@ -0,0 +1,178 @@ +/* +© Copyright IBM Corporation 2017 + +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 main + +import ( + "context" + "strings" + "testing" + + "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" +) + +const image string = "mq-devserver:latest-x86_64" + +func TestLicenseNotSet(t *testing.T) { + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Image: image, + } + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id) + rc := waitForContainer(t, cli, id, 5) + if rc != 1 { + t.Errorf("Expected rc=1, got rc=%v", rc) + } +} + +func TestLicenseView(t *testing.T) { + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Image: image, + Env: []string{"LICENSE=view"}, + } + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id) + rc := waitForContainer(t, cli, id, 5) + if rc != 1 { + t.Errorf("Expected rc=1, got rc=%v", rc) + } + l := inspectLogs(t, cli, id) + const s string = "terms" + if !strings.Contains(l, s) { + t.Errorf("Expected license string to contain \"%v\", got %v", s, l) + } +} + +func TestGoldenPath(t *testing.T) { + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Image: image, + Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, + //ExposedPorts: ports, + ExposedPorts: nat.PortSet{ + "1414/tcp": struct{}{}, + }, + } + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id) + waitForReady(t, cli, id) +} + +func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) { + search := "QMNAME(" + expectedName + ")" + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Image: image, + Env: []string{"LICENSE=accept"}, + Hostname: hostName, + ExposedPorts: nat.PortSet{ + "1414/tcp": struct{}{}, + }, + } + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id) + waitForReady(t, cli, id) + _, out := execContainer(t, cli, id, []string{"dspmq"}) + if !strings.Contains(out, search) { + t.Errorf("Expected result of running dspmq to contain name=%v, got name=%v", search, out) + } +} +func TestNoQueueManagerName(t *testing.T) { + utilTestNoQueueManagerName(t, "test", "test") +} + +func TestNoQueueManagerNameInvalidHostname(t *testing.T) { + utilTestNoQueueManagerName(t, "test-1", "test1") +} + +// TestWithVolume runs a container with a Docker volume, then removes that +// container and starts a new one with same volume. +func TestWithVolume(t *testing.T) { + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + vol := createVolume(t, cli) + defer removeVolume(t, cli, vol.Name) + containerConfig := container.Config{ + Image: image, + Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, + } + hostConfig := container.HostConfig{ + Binds: []string{ + coverageBind(t), + //"coverage:/var/coverage", + vol.Name + ":/mnt/mqm", + }, + } + networkingConfig := network.NetworkingConfig{} + ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()) + if err != nil { + t.Fatal(err) + } + startContainer(t, cli, ctr.ID) + // TODO: If this test gets an error waiting for readiness, the first container might not get cleaned up + waitForReady(t, cli, ctr.ID) + + // Delete the first container + cleanContainer(t, cli, ctr.ID) + + // Start a new container with the same volume + ctr2, err := cli.ContainerCreate(context.Background(), &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) +} + +func TestNoVolumeWithRestart(t *testing.T) { + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Image: image, + Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, + //ExposedPorts: ports, + ExposedPorts: nat.PortSet{ + "1414/tcp": struct{}{}, + }, + } + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id) + waitForReady(t, cli, id) + stopContainer(t, cli, id) + startContainer(t, cli, id) + waitForReady(t, cli, id) +} diff --git a/test/docker-devserver/docker_api_test_util.go b/test/docker-devserver/docker_api_test_util.go new file mode 100644 index 0000000..f9a641f --- /dev/null +++ b/test/docker-devserver/docker_api_test_util.go @@ -0,0 +1,265 @@ +/* +© Copyright IBM Corporation 2017 + +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 main + +import ( + "bytes" + "context" + "io/ioutil" + "log" + "os" + "path/filepath" + "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/go-connections/nat" +) + +func coverageBind(t *testing.T) string { + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + return filepath.Join(dir, "coverage") + ":/var/coverage" +} + +func cleanContainer(t *testing.T, cli *client.Client, ID string) { + i, err := cli.ContainerInspect(context.Background(), ID) + if err == nil { + // Log the results and continue + t.Logf("Inspected container %v: %#v", ID, i) + } + t.Logf("Killing container: %v", ID) + // Kill the container. This allows the coverage output to be generated. + err = cli.ContainerKill(context.Background(), ID, "SIGTERM") + if err != nil { + // Just log the error and continue + t.Log(err) + } + //waitForContainer(t, cli, ID, 20, container.WaitConditionNotRunning) + + // TODO: This is probably no longer necessary + time.Sleep(20 * time.Second) + // Log the container output for any container we're about to delete + t.Logf("Console log from container %v:\n%v", ID, inspectLogs(t, cli, ID)) + + t.Logf("Removing container: %s", ID) + opts := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + err = cli.ContainerRemove(context.Background(), ID, opts) + if err != nil { + t.Error(err) + } +} + +func runContainer(t *testing.T, cli *client.Client, containerConfig *container.Config) string { + t.Logf("Running container") + hostConfig := container.HostConfig{ + PortBindings: nat.PortMap{ + "1414/tcp": []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: "1414", + }, + }, + }, + } + networkingConfig := network.NetworkingConfig{} + ctr, err := cli.ContainerCreate(context.Background(), containerConfig, &hostConfig, &networkingConfig, t.Name()) + if err != nil { + t.Fatal(err) + } + startContainer(t, cli, ctr.ID) + return ctr.ID +} + +func startContainer(t *testing.T, cli *client.Client, ID string) { + t.Logf("Starting container: %v", ID) + startOptions := types.ContainerStartOptions{} + err := cli.ContainerStart(context.Background(), ID, startOptions) + if err != nil { + t.Fatal(err) + } +} + +func stopContainer(t *testing.T, cli *client.Client, ID string) { + t.Logf("Stopping container: %v", ID) + timeout := 10 * time.Second + err := cli.ContainerStop(context.Background(), ID, &timeout) //Duration(20)*time.Second) + if err != nil { + t.Fatal(err) + } +} + +// waitForContainer waits until a container has exited +func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout int64) int64 { + //ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + //defer cancel() + rc, err := cli.ContainerWait(context.Background(), ID) + // err := <-errC + if err != nil { + t.Fatal(err) + } + // wait := <-waitC + return rc +} + +// execContainer runs the specified command inside the container, returning the +// exit code and the stdout/stderr string. +func execContainer(t *testing.T, cli *client.Client, ID string, cmd []string) (int, string) { + config := types.ExecConfig{ + User: "mqm", + Privileged: false, + Tty: false, + AttachStdin: false, + 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, config) + if err != nil { + t.Fatal(err) + } + cli.ContainerExecStart(context.Background(), resp.ID, types.ExecStartCheck{ + Detach: false, + Tty: false, + }) + if err != nil { + t.Fatal(err) + } + inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID) + if err != nil { + t.Fatal(err) + } + // TODO: For some reason, each line seems to start with an extra, random character + buf, err := ioutil.ReadAll(hijack.Reader) + if err != nil { + t.Fatal(err) + } + hijack.Close() + return inspect.ExitCode, string(buf) +} + +func waitForReady(t *testing.T, cli *client.Client, ID string) { + for { + resp, err := cli.ContainerExecCreate(context.Background(), ID, types.ExecConfig{ + User: "mqm", + Privileged: false, + Tty: false, + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Detach: false, + Cmd: []string{"chkmqready"}, + }) + if err != nil { + t.Fatal(err) + } + cli.ContainerExecStart(context.Background(), resp.ID, types.ExecStartCheck{ + Detach: false, + Tty: false, + }) + if err != nil { + t.Fatal(err) + } + inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID) + if err != nil { + t.Fatal(err) + } + if inspect.ExitCode == 0 { + t.Log("MQ is ready") + return + } + } +} + +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 { + name := "test" + t.Logf("Creating network: %v", name) + opts := types.NetworkCreate{} + net, err := cli.NetworkCreate(context.Background(), name, opts) + if err != nil { + t.Fatal(err) + } + t.Logf("Created network %v with ID %v", name, net.ID) + return net.ID +} + +func removeNetwork(t *testing.T, cli *client.Client, ID string) { + t.Logf("Removing network ID: %v", ID) + err := cli.NetworkRemove(context.Background(), ID) + if err != nil { + t.Fatal(err) + } +} + +func createVolume(t *testing.T, cli *client.Client) types.Volume { + v, err := cli.VolumeCreate(context.Background(), volume.VolumesCreateBody{ + Driver: "local", + DriverOpts: map[string]string{}, + Labels: map[string]string{}, + Name: t.Name(), + }) + if err != nil { + t.Fatal(err) + } + t.Logf("Created volume %v", t.Name()) + return v +} + +func removeVolume(t *testing.T, cli *client.Client, name string) { + t.Logf("Removing volume %v", name) + err := cli.VolumeRemove(context.Background(), name, true) + if err != nil { + t.Fatal(err) + } +} + +func inspectLogs(t *testing.T, cli *client.Client, 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, + }) + if err != nil { + log.Fatal(err) + } + + buf := new(bytes.Buffer) + buf.ReadFrom(reader) + return buf.String() +} diff --git a/test/docker-devserver/main.go b/test/docker-devserver/main.go new file mode 100644 index 0000000..d0c17af --- /dev/null +++ b/test/docker-devserver/main.go @@ -0,0 +1,19 @@ +/* +© Copyright IBM Corporation 2017 + +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 main + +func main() { +}