Merge pull request #20 from arthurbarr/master

Latest build and test changes
This commit is contained in:
Arthur Barr
2017-12-06 11:23:28 +00:00
committed by GitHub
10 changed files with 565 additions and 108 deletions

View File

@@ -13,17 +13,12 @@ before_install:
- sudo apt-get update
- sudo apt-get -y install docker-ce
- curl https://glide.sh/get | sh
- curl -LO https://github.com/golang/dep/releases/download/v0.3.0/dep-linux-amd64.zip
- unzip dep-linux-amd64.zip
- sudo mv dep /usr/local/bin
- rm dep-linux-amd64.zip
- sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.3.2/dep-linux-amd64
- sudo chmod +x /usr/local/bin/dep
install:
- echo nothing
before_script:
- make deps
script:
- make build-devserver
- make test-devserver

View File

@@ -16,7 +16,7 @@ BUILD_SERVER_CONTAINER=build-server
DOCKER_TAG_ARCH ?= $(shell uname -m)
# By default, all Docker client commands are run inside a Docker container.
# This means that newer features of the client can be used, even with an older daemon.
DOCKER ?= docker run --tty --interactive --rm --volume /var/run/docker.sock:/var/run/docker.sock --volume "$(CURDIR)":/var/src --workdir /var/src docker:stable docker
DOCKER ?= docker
DOCKER_TAG ?= latest-$(DOCKER_TAG_ARCH)
DOCKER_REPO_DEVSERVER ?= mq-devserver
DOCKER_REPO_ADVANCEDSERVER ?= mq-advancedserver
@@ -43,6 +43,14 @@ TEST_OPTS_DOCKER ?=
# Options to `go test` for the Kubernetes tests
TEST_OPTS_KUBERNETES ?=
TEST_IMAGE ?= $(DOCKER_FULL_ADVANCEDSERVER)
NUM_CPU=$(shell docker info --format "{{ .NCPU }}")
.PHONY: vars
vars:
echo $(DOCKER_SERVER_VERSION_MAJOR)
echo $(DOCKER_SERVER_VERSION_MINOR)
echo $(DOCKER_CLIENT_VERSION_MAJOR)
echo $(DOCKER_CLIENT_VERSION_MINOR)
.PHONY: default
default: build-devserver test
@@ -75,26 +83,37 @@ downloads: downloads/$(MQ_ARCHIVE_DEV)
.PHONY: deps
deps:
glide install --strip-vendor
# Vendor Go dependencies for the Docker tests
test/docker/vendor:
cd test/docker && dep ensure -vendor-only
# Vendor Go dependencies for the Kubernetes tests
test/kubernetes/vendor:
cd test/docker && dep ensure -vendor-only
cd test/kubernetes && dep ensure -vendor-only
.PHONY: build-cov
build-cov:
mkdir -p build
cd build; go test -c -covermode=count ../cmd/runmqserver
# Shortcut to just run the unit tests
.PHONY: test-unit
test-unit:
docker build --target builder --file Dockerfile-server .
.PHONY: test-advancedserver
test-advancedserver:
test-advancedserver: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_FULL_ADVANCEDSERVER) on Docker"$(END)))
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_ADVANCEDSERVER) go test $(TEST_OPTS_DOCKER)
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_ADVANCEDSERVER) go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
.PHONY: test-devserver
test-devserver:
test-devserver: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_FULL_DEVSERVER) on Docker"$(END)))
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_DEVSERVER) go test
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_DEVSERVER) go test -parallel $(NUM_CPU)
.PHONY: test-advancedserver-cover
test-advancedserver-cover:
test-advancedserver-cover: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_REPO_ADVANCEDSERVER) on Docker with code coverage"$(END)))
rm -f ./coverage/unit*.cov
# Run unit tests with coverage, for each package under 'internal'
@@ -115,11 +134,11 @@ test-advancedserver-cover:
go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html
.PHONY: test-kubernetes-devserver
test-kubernetes-devserver:
test-kubernetes-devserver: test/kubernetes/vendor
$(call test-kubernetes,$(DOCKER_REPO_DEVSERVER),$(DOCKER_TAG),"../../charts/ibm-mqadvanced-server-dev")
.PHONY: test-kubernetes-advancedserver
test-kubernetes-advancedserver:
test-kubernetes-advancedserver: test/kubernetes/vendor
$(call test-kubernetes,$(DOCKER_REPO_ADVANCEDSERVER),$(DOCKER_TAG),"../../charts/ibm-mqadvanced-server-prod")
define test-kubernetes
@@ -139,9 +158,10 @@ define docker-build-mq
--volume "$(realpath ./downloads/)":/usr/share/nginx/html:ro \
--detach \
nginx:alpine
# Make sure we have the latest base image
$(DOCKER) pull ubuntu:16.04
# Build the new image
$(DOCKER) build \
--pull \
--tag $1 \
--file $2 \
--network build \
@@ -153,24 +173,30 @@ define docker-build-mq
. ; $(DOCKER) kill $(BUILD_SERVER_CONTAINER) && $(DOCKER) network rm build
endef
DOCKER_SERVER_VERSION=$(shell docker version --format "{{ .Server.Version }}")
DOCKER_CLIENT_VERSION=$(shell docker version --format "{{ .Client.Version }}")
.PHONY: docker-version
docker-version:
@test "$(word 1,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "17" || (echo "Error: Docker client 17.05 or greater is required" && exit 1)
@test "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05" || (echo "Error: Docker client 17.05 or greater is required" && exit 1)
@test "$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -ge "17" || (echo "Error: Docker server 17.05 or greater is required" && exit 1)
@test "$(word 2,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -ge "05" || (echo "Error: Docker server 17.05 or greater is required" && exit 1)
.PHONY: build-advancedserver
build-advancedserver: downloads/$(MQ_ARCHIVE)
build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version
$(info $(SPACER)$(shell printf $(TITLE)"Build $(DOCKER_FULL_ADVANCEDSERVER)"$(END)))
$(call docker-build-mq,$(DOCKER_FULL_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION))
$(DOCKER) tag $(DOCKER_FULL_ADVANCEDSERVER) $(DOCKER_REPO_ADVANCEDSERVER):$(MQ_VERSION)-$(DOCKER_TAG_ARCH)
.PHONY: build-devserver
build-devserver: downloads/$(MQ_ARCHIVE_DEV)
ifneq "x86_64" "$(shell uname -m)"
    $(error MQ Advanced for Developers is only available for x86_64 architecture)
else
build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version
@test "$(shell uname -m)" = "x86_64" || (echo "Error: MQ Advanced for Developers is only available for x86_64 architecture" && exit 1)
$(info $(shell printf $(TITLE)"Build $(DOCKER_FULL_DEVSERVER)"$(END)))
$(call docker-build-mq,$(DOCKER_FULL_DEVSERVER),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
$(DOCKER) tag $(DOCKER_FULL_DEVSERVER) $(DOCKER_REPO_DEVSERVER):$(MQ_VERSION)-$(DOCKER_TAG_ARCH)
endif
.PHONY: build-advancedserver-cover
build-advancedserver-cover:
build-advancedserver-cover: docker-version
$(DOCKER) build -t $(DOCKER_REPO_ADVANCEDSERVER):cover -f Dockerfile-server.cover .
# .PHONY: build-web

View File

@@ -36,7 +36,8 @@ func resolveLicenseFile() string {
return "Chinese_TW.txt"
case strings.HasPrefix(lang, "zh"):
return "Chinese.txt"
case strings.HasPrefix(lang, "cs"):
// Differentiate Czech (cs) and Kashubian (csb)
case strings.HasPrefix(lang, "cs") && !strings.HasPrefix(lang, "csb"):
return "Czech.txt"
case strings.HasPrefix(lang, "fr"):
return "French.txt"
@@ -50,7 +51,8 @@ func resolveLicenseFile() string {
return "Italian.txt"
case strings.HasPrefix(lang, "ja"):
return "Japanese.txt"
case strings.HasPrefix(lang, "ko"):
// Differentiate Korean (ko) from Konkani (kok)
case strings.HasPrefix(lang, "ko") && !strings.HasPrefix(lang, "kok"):
return "Korean.txt"
case strings.HasPrefix(lang, "lt"):
return "Lithuanian.txt"

View File

@@ -0,0 +1,282 @@
/*
© 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 (
"os"
"testing"
)
var licenseTests = []struct {
in string
out string
}{
{"en_US.UTF_8", "English.txt"},
{"en_US.ISO-8859-15", "English.txt"},
{"es_GB", "Spanish.txt"},
{"el_ES.UTF_8", "Greek.txt"},
// Cover a wide variety of valid values
{"af", "English.txt"},
{"af_ZA", "English.txt"},
{"ar", "English.txt"},
{"ar_AE", "English.txt"},
{"ar_BH", "English.txt"},
{"ar_DZ", "English.txt"},
{"ar_EG", "English.txt"},
{"ar_IQ", "English.txt"},
{"ar_JO", "English.txt"},
{"ar_KW", "English.txt"},
{"ar_LB", "English.txt"},
{"ar_LY", "English.txt"},
{"ar_MA", "English.txt"},
{"ar_OM", "English.txt"},
{"ar_QA", "English.txt"},
{"ar_SA", "English.txt"},
{"ar_SY", "English.txt"},
{"ar_TN", "English.txt"},
{"ar_YE", "English.txt"},
{"az", "English.txt"},
{"az_AZ", "English.txt"},
{"az_AZ", "English.txt"},
{"be", "English.txt"},
{"be_BY", "English.txt"},
{"bg", "English.txt"},
{"bg_BG", "English.txt"},
{"bs_BA", "English.txt"},
{"ca", "English.txt"},
{"ca_ES", "English.txt"},
{"cs", "Czech.txt"},
{"cs_CZ", "Czech.txt"},
{"csb_PL", "English.txt"},
{"cy", "English.txt"},
{"cy_GB", "English.txt"},
{"da", "English.txt"},
{"da_DK", "English.txt"},
{"de", "German.txt"},
{"de_AT", "German.txt"},
{"de_CH", "German.txt"},
{"de_DE", "German.txt"},
{"de_LI", "German.txt"},
{"de_LU", "German.txt"},
{"dv", "English.txt"},
{"dv_MV", "English.txt"},
{"el", "Greek.txt"},
{"el_GR", "Greek.txt"},
{"en", "English.txt"},
{"en_AU", "English.txt"},
{"en_BZ", "English.txt"},
{"en_CA", "English.txt"},
{"en_CB", "English.txt"},
{"en_GB", "English.txt"},
{"en_IE", "English.txt"},
{"en_JM", "English.txt"},
{"en_NZ", "English.txt"},
{"en_PH", "English.txt"},
{"en_TT", "English.txt"},
{"en_US", "English.txt"},
{"en_ZA", "English.txt"},
{"en_ZW", "English.txt"},
{"eo", "English.txt"},
{"es", "Spanish.txt"},
{"es_AR", "Spanish.txt"},
{"es_BO", "Spanish.txt"},
{"es_CL", "Spanish.txt"},
{"es_CO", "Spanish.txt"},
{"es_CR", "Spanish.txt"},
{"es_DO", "Spanish.txt"},
{"es_EC", "Spanish.txt"},
{"es_ES", "Spanish.txt"},
{"es_ES", "Spanish.txt"},
{"es_GT", "Spanish.txt"},
{"es_HN", "Spanish.txt"},
{"es_MX", "Spanish.txt"},
{"es_NI", "Spanish.txt"},
{"es_PA", "Spanish.txt"},
{"es_PE", "Spanish.txt"},
{"es_PR", "Spanish.txt"},
{"es_PY", "Spanish.txt"},
{"es_SV", "Spanish.txt"},
{"es_UY", "Spanish.txt"},
{"es_VE", "Spanish.txt"},
{"et", "English.txt"},
{"et_EE", "English.txt"},
{"eu", "English.txt"},
{"eu_ES", "English.txt"},
{"fa", "English.txt"},
{"fa_IR", "English.txt"},
{"fi", "English.txt"},
{"fi_FI", "English.txt"},
{"fo", "English.txt"},
{"fo_FO", "English.txt"},
{"fr", "French.txt"},
{"fr_BE", "French.txt"},
{"fr_CA", "French.txt"},
{"fr_CH", "French.txt"},
{"fr_FR", "French.txt"},
{"fr_LU", "French.txt"},
{"fr_MC", "French.txt"},
{"gl", "English.txt"},
{"gl_ES", "English.txt"},
{"gu", "English.txt"},
{"gu_IN", "English.txt"},
{"he", "English.txt"},
{"he_IL", "English.txt"},
{"hi", "English.txt"},
{"hi_IN", "English.txt"},
{"hr", "English.txt"},
{"hr_BA", "English.txt"},
{"hr_HR", "English.txt"},
{"hu", "English.txt"},
{"hu_HU", "English.txt"},
{"hy", "English.txt"},
{"hy_AM", "English.txt"},
{"id", "Indonesian.txt"},
{"id_ID", "Indonesian.txt"},
{"is", "English.txt"},
{"is_IS", "English.txt"},
{"it", "Italian.txt"},
{"it_CH", "Italian.txt"},
{"it_IT", "Italian.txt"},
{"ja", "Japanese.txt"},
{"ja_JP", "Japanese.txt"},
{"ka", "English.txt"},
{"ka_GE", "English.txt"},
{"kk", "English.txt"},
{"kk_KZ", "English.txt"},
{"kn", "English.txt"},
{"kn_IN", "English.txt"},
{"ko", "Korean.txt"},
{"ko_KR", "Korean.txt"},
{"kok", "English.txt"},
{"kok_IN", "English.txt"},
{"ky", "English.txt"},
{"ky_KG", "English.txt"},
{"lt", "Lithuanian.txt"},
{"lt_LT", "Lithuanian.txt"},
{"lv", "English.txt"},
{"lv_LV", "English.txt"},
{"mi", "English.txt"},
{"mi_NZ", "English.txt"},
{"mk", "English.txt"},
{"mk_MK", "English.txt"},
{"mn", "English.txt"},
{"mn_MN", "English.txt"},
{"mr", "English.txt"},
{"mr_IN", "English.txt"},
{"ms", "English.txt"},
{"ms_BN", "English.txt"},
{"ms_MY", "English.txt"},
{"mt", "English.txt"},
{"mt_MT", "English.txt"},
{"nb", "English.txt"},
{"nb_NO", "English.txt"},
{"nl", "English.txt"},
{"nl_BE", "English.txt"},
{"nl_NL", "English.txt"},
{"nn_NO", "English.txt"},
{"ns", "English.txt"},
{"ns_ZA", "English.txt"},
{"pa", "English.txt"},
{"pa_IN", "English.txt"},
{"pl", "Polish.txt"},
{"pl_PL", "Polish.txt"},
{"ps", "English.txt"},
{"ps_AR", "English.txt"},
{"pt", "Portugese.txt"},
{"pt_BR", "Portugese.txt"},
{"pt_PT", "Portugese.txt"},
{"qu", "English.txt"},
{"qu_BO", "English.txt"},
{"qu_EC", "English.txt"},
{"qu_PE", "English.txt"},
{"ro", "English.txt"},
{"ro_RO", "English.txt"},
{"ru", "Russian.txt"},
{"ru_RU", "Russian.txt"},
{"sa", "English.txt"},
{"sa_IN", "English.txt"},
{"se", "English.txt"},
{"se_FI", "English.txt"},
{"se_FI", "English.txt"},
{"se_FI", "English.txt"},
{"se_NO", "English.txt"},
{"se_NO", "English.txt"},
{"se_NO", "English.txt"},
{"se_SE", "English.txt"},
{"se_SE", "English.txt"},
{"se_SE", "English.txt"},
{"sk", "English.txt"},
{"sk_SK", "English.txt"},
{"sl", "Slovenian.txt"},
{"sl_SI", "Slovenian.txt"},
{"sq", "English.txt"},
{"sq_AL", "English.txt"},
{"sr_BA", "English.txt"},
{"sr_BA", "English.txt"},
{"sr_SP", "English.txt"},
{"sr_SP", "English.txt"},
{"sv", "English.txt"},
{"sv_FI", "English.txt"},
{"sv_SE", "English.txt"},
{"sw", "English.txt"},
{"sw_KE", "English.txt"},
{"syr", "English.txt"},
{"syr_SY", "English.txt"},
{"ta", "English.txt"},
{"ta_IN", "English.txt"},
{"te", "English.txt"},
{"te_IN", "English.txt"},
{"th", "English.txt"},
{"th_TH", "English.txt"},
{"tl", "English.txt"},
{"tl_PH", "English.txt"},
{"tn", "English.txt"},
{"tn_ZA", "English.txt"},
{"tr", "Turkish.txt"},
{"tr_TR", "Turkish.txt"},
{"tt", "English.txt"},
{"tt_RU", "English.txt"},
{"ts", "English.txt"},
{"uk", "English.txt"},
{"uk_UA", "English.txt"},
{"ur", "English.txt"},
{"ur_PK", "English.txt"},
{"uz", "English.txt"},
{"uz_UZ", "English.txt"},
{"uz_UZ", "English.txt"},
{"vi", "English.txt"},
{"vi_VN", "English.txt"},
{"xh", "English.txt"},
{"xh_ZA", "English.txt"},
{"zh", "Chinese.txt"},
{"zh_CN", "Chinese.txt"},
{"zh_HK", "Chinese.txt"},
{"zh_MO", "Chinese.txt"},
{"zh_SG", "Chinese.txt"},
{"zh_TW", "Chinese_TW.txt"},
{"zu", "English.txt"},
{"zu_ZA", "English.txt"},
}
func TestResolveLicenseFile(t *testing.T) {
for _, table := range licenseTests {
os.Setenv("LANG", table.in)
f := resolveLicenseFile()
if f != table.out {
t.Errorf("resolveLicenseFile() with LANG=%v - expected %v, got %v", table.in, table.out, f)
}
}
}

View File

@@ -19,6 +19,7 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
@@ -30,6 +31,20 @@ import (
"github.com/ibm-messaging/mq-container/internal/name"
)
var debug = false
func logDebug(msg string) {
if debug {
log.Printf("DEBUG: %v", msg)
}
}
func logDebugf(format string, args ...interface{}) {
if debug {
log.Printf("DEBUG: %v", fmt.Sprintf(format, args...))
}
}
// createDirStructure creates the default MQ directory structure under /var/mqm
func createDirStructure() error {
out, _, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-s")
@@ -127,6 +142,10 @@ func stopQueueManager(name string) error {
}
func doMain() error {
debugEnv, ok := os.LookupEnv("DEBUG")
if ok && (debugEnv == "true" || debugEnv == "1") {
debug = true
}
accepted, err := checkLicense()
if err != nil {
return err

View File

@@ -50,11 +50,13 @@ func signalHandler(qmgr string) chan int {
// End the goroutine
return
case <-reapSignals:
logDebug("Received SIGCHLD signal")
reapZombies()
case job := <-control:
switch {
case job == startReaping:
// Add SIGCHLD to the list of signals we're listening to
logDebug("Listening for SIGCHLD signals")
signal.Notify(reapSignals, syscall.SIGCHLD)
case job == reapNow:
reapZombies()
@@ -75,5 +77,6 @@ func reapZombies() {
if pid == 0 || err == unix.ECHILD {
return
}
logDebugf("Reaped PID %v", pid)
}
}

View File

@@ -2,12 +2,13 @@
## Prerequisites
You need to ensure you have the following tools installed:
* [Docker](https://www.docker.com/) V17.05 or later
* [GNU make](https://www.gnu.org/software/make/)
* [Docker](https://www.docker.com/)
You might also need the following tools installed:
* [Go](https://golang.org/) - only needed for running the tests
* [Glide](https://glide.sh/)
* [dep](https://github.com/golang/dep) (official Go dependency management tool)
* make
* [Glide](https://glide.sh/) - only needed if you update the main dependencies
* [dep](https://github.com/golang/dep) (official Go dependency management tool) - only needed to prepare for running the tests
* [Helm](https://helm.sh) - only needed for running the Kubernetes tests
For running the Kubernetes tests, a Kubernetes environment is needed, for example [Minikube](https://github.com/kubernetes/minikube) or [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/).
@@ -24,6 +25,12 @@ You can build a different version of MQ by setting the `MQ_VERSION` environment
MQ_VERSION=9.0.3.0 make build-advancedserver
```
If you have an MQ archive file with a different file name, you can specify a particular file (which must be in the `downloads` directory). You should also specify the MQ version, so that the resulting image is tagged correctly, for example:
```bash
MQ_ARCHIVE=mq-1.2.3.4.tar.gz MQ_VERSION=1.2.3.4 build-advancedserver
```
## Running the tests
There are three main sets of tests:
@@ -50,6 +57,12 @@ or:
make test-advancedserver
```
You can pass parameters to `go test` with an environment variable. For example, to run the "TestGoldenPath" test, run the following command::
```
TEST_OPTS_DOCKER="-run TestGoldenPath" make test-advancedserver
```
### Running the Docker tests with code coverage
You can produce code coverage results from the Docker tests by running the following:

View File

@@ -87,12 +87,10 @@ find /opt/mqm -name '*.tar.gz' -delete
rm -f /etc/apt/sources.list.d/IBM_MQ.list
rm -rf ${DIR_EXTRACT}
# Apply any bug fixes not included in base Ubuntu or MQ image.
#### Apply any bug fixes not included in base Ubuntu or MQ image.
# Don't upgrade everything based on Docker best practices https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run
apt-get upgrade -y libkrb5-26-heimdal
apt-get upgrade -y libexpat1
# End of bug fixes
apt-get upgrade -y libdb5.3
#### End of bug fixes
# Clean up cached apt files
rm -rf /var/lib/apt/lists/*

View File

@@ -17,8 +17,10 @@ package main
import (
"context"
"strconv"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
@@ -27,6 +29,7 @@ import (
)
func TestLicenseNotSet(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
@@ -41,6 +44,7 @@ func TestLicenseNotSet(t *testing.T) {
}
func TestLicenseView(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
@@ -61,23 +65,50 @@ func TestLicenseView(t *testing.T) {
}
}
// TestGoldenPath starts a queue manager successfully
func TestGoldenPath(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
//ExposedPorts: ports,
ExposedPorts: nat.PortSet{
"1414/tcp": struct{}{},
},
// ExposedPorts: nat.PortSet{
// "1414/tcp": struct{}{},
// },
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
waitForReady(t, cli, id)
}
// TestSecurityVulnerabilities checks for any vulnerabilities in the image, as reported
// by Ubuntu
func TestSecurityVulnerabilities(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
containerConfig := container.Config{
// Override the entrypoint to make "apt" only receive security updates, then check for updates
Entrypoint: []string{"bash", "-c", "source /etc/os-release && echo \"deb http://security.ubuntu.com/ubuntu/ ${VERSION_CODENAME}-security main restricted\" > /etc/apt/sources.list && apt-get update 2>&1 >/dev/null && apt-get --simulate -qq upgrade"},
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
// rc is the return code from apt-get
rc := waitForContainer(t, cli, id, 10)
if rc != 0 {
t.Fatalf("Expected success, got %v", rc)
}
log := inspectLogs(t, cli, id)
lines := strings.Split(strings.TrimSpace(log), "\n")
if len(lines) > 0 && lines[0] != "" {
t.Errorf("Expected no vulnerabilities, found the following:\n%v", log)
}
}
func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) {
search := "QMNAME(" + expectedName + ")"
cli, err := client.NewEnvClient()
@@ -87,29 +118,29 @@ func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName stri
containerConfig := container.Config{
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"})
out := execContainerWithOutput(t, cli, id, "mqm", []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) {
t.Parallel()
utilTestNoQueueManagerName(t, "test", "test")
}
func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
@@ -148,17 +179,16 @@ func TestWithVolume(t *testing.T) {
waitForReady(t, cli, ctr2.ID)
}
// TestNoVolumeWithRestart ensures a queue manager container can be stopped
// and restarted cleanly
func TestNoVolumeWithRestart(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
containerConfig := container.Config{
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)
@@ -168,8 +198,9 @@ func TestNoVolumeWithRestart(t *testing.T) {
waitForReady(t, cli, id)
}
// Test the case where `crtmqm` will fail
// TestCreateQueueManagerFail causes a failure of `crtmqm`
func TestCreateQueueManagerFail(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
@@ -178,10 +209,6 @@ func TestCreateQueueManagerFail(t *testing.T) {
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
//ExposedPorts: ports,
ExposedPorts: nat.PortSet{
"1414/tcp": struct{}{},
},
// Override the entrypoint to create the queue manager directory, but leave it empty.
// This will cause `crtmqm` to return with an exit code of 2.
Entrypoint: []string{"bash", "-c", "mkdir -p /mnt/mqm/data && mkdir -p /var/mqm/qmgrs/qm1 && exec " + oldEntrypoint},
@@ -194,8 +221,9 @@ func TestCreateQueueManagerFail(t *testing.T) {
}
}
// Test the case where `strmqm` will fail
// TestStartQueueManagerFail causes a failure of `strmqm`
func TestStartQueueManagerFail(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
@@ -204,10 +232,6 @@ func TestStartQueueManagerFail(t *testing.T) {
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
//ExposedPorts: ports,
ExposedPorts: nat.PortSet{
"1414/tcp": struct{}{},
},
// Override the entrypoint to replace `crtmqm` with a no-op script.
// This will cause `strmqm` to return with an exit code of 16.
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\n' > /opt/mqm/bin/crtmqm && exec " + oldEntrypoint},
@@ -219,3 +243,87 @@ func TestStartQueueManagerFail(t *testing.T) {
t.Errorf("Expected rc=1, got rc=%v", rc)
}
}
// TestVolumeUnmount runs a queue manager with a volume, and then forces an
// unmount of the volume. The health check should then fail.
// This simulates behaviour seen in some cloud environments, where network
// attached storage gets unmounted.
func TestVolumeUnmount(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
vol := createVolume(t, cli)
defer removeVolume(t, cli, vol.Name)
containerConfig := container.Config{
Image: imageName(),
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
}
hostConfig := container.HostConfig{
// SYS_ADMIN capability is required to unmount file systems
CapAdd: []string{
"SYS_ADMIN",
},
Binds: []string{
coverageBind(t),
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)
defer cleanContainer(t, cli, ctr.ID)
waitForReady(t, cli, ctr.ID)
// Unmount the volume as root
rc := execContainerWithExitCode(t, cli, ctr.ID, "root", []string{"umount", "-l", "-f", "/mnt/mqm"})
if rc != 0 {
t.Fatalf("Expected umount to work with rc=0, got %v", rc)
}
time.Sleep(3 * time.Second)
rc = execContainerWithExitCode(t, cli, ctr.ID, "mqm", []string{"chkmqhealthy"})
if rc == 0 {
t.Errorf("Expected chkmqhealthy to fail")
t.Logf(execContainerWithOutput(t, cli, ctr.ID, "mqm", []string{"df"}))
t.Logf(execContainerWithOutput(t, cli, ctr.ID, "mqm", []string{"ps", "-ef"}))
}
}
// TestZombies starts a queue manager, then causes a zombie process to be
// created, then checks that no zombies exist (runmqserver should reap them)
func TestZombies(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
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{}{},
},
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
waitForReady(t, cli, id)
// Kill an MQ process with children. After it is killed, its children
// will be adopted by PID 1, and should then be reaped when they die.
out := execContainerWithOutput(t, cli, id, "mqm", []string{"pkill", "--signal", "kill", "-c", "amqzxma0"})
if out == "0" {
t.Fatalf("Expected pkill to kill a process, got %v", out)
}
time.Sleep(3 * time.Second)
// Create a zombie process for up to ten seconds
out = execContainerWithOutput(t, cli, id, "mqm", []string{"bash", "-c", "ps -lA | grep '^. Z' | wc -l"})
count, err := strconv.Atoi(out)
if err != nil {
t.Fatal(err)
}
if count != 0 {
t.Fatalf("Expected zombies=0, got %v", count)
}
}

View File

@@ -23,6 +23,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
@@ -31,7 +32,7 @@ import (
"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"
"github.com/docker/docker/pkg/stdcopy"
)
func imageName() string {
@@ -97,14 +98,14 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
// if coverage
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
hostConfig := container.HostConfig{
PortBindings: nat.PortMap{
"1414/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: "1414",
},
},
},
// PortBindings: nat.PortMap{
// "1414/tcp": []nat.PortBinding{
// {
// HostIP: "0.0.0.0",
// HostPort: "1414",
// },
// },
// },
Binds: []string{
coverageBind(t),
},
@@ -141,11 +142,11 @@ func getCoverageExitCode(t *testing.T, orig int64) int64 {
f := filepath.Join(coverageDir(t), "exitCode")
_, err := os.Stat(f)
if err != nil {
t.Log(err)
//t.Log(err)
return orig
}
// Remove the file, ready for the next test
//defer os.Remove(f)
defer os.Remove(f)
buf, err := ioutil.ReadFile(f)
if err != nil {
t.Log(err)
@@ -179,11 +180,45 @@ func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout int64
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) {
// execContainerWithExitCode runs a command in a running container, and returns the exit code
// Note: due to a bug in Docker/Moby code, you always get an exit code of 0 if you attach to the
// container to get output. This is why these are two separate commands.
func execContainerWithExitCode(t *testing.T, cli *client.Client, ID string, user string, cmd []string) int {
config := types.ExecConfig{
User: "mqm",
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)
}
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)
}
return inspect.ExitCode
}
// execContainerWithOutput runs a command in a running container, and returns the output from stdout/stderr
// Note: due to a bug in Docker/Moby code, you always get an exit code of 0 if you attach to the
// container to get output. This is why these are two separate commands.
func execContainerWithOutput(t *testing.T, cli *client.Client, ID string, user string, cmd []string) string {
config := types.ExecConfig{
User: user,
Privileged: false,
Tty: false,
AttachStdin: false,
@@ -207,46 +242,19 @@ func execContainer(t *testing.T, cli *client.Client, ID string, cmd []string) (i
if err != nil {
t.Fatal(err)
}
inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID)
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)
log.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)
return strings.TrimSpace(buf.String())
}
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 {
rc := execContainerWithExitCode(t, cli, ID, "mqm", []string{"chkmqready"})
if rc == 0 {
t.Log("MQ is ready")
return
}
@@ -313,8 +321,11 @@ func inspectLogs(t *testing.T, cli *client.Client, ID string) string {
if err != nil {
log.Fatal(err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
// Each output line has a header, which needs to be removed
_, err = stdcopy.StdCopy(buf, buf, reader)
if err != nil {
log.Fatal(err)
}
return buf.String()
}