Merge pull request #19 from arthurbarr/master

Miscellaneous improvements
This commit is contained in:
Arthur Barr
2017-11-28 15:58:45 +00:00
committed by GitHub
18 changed files with 531 additions and 252 deletions

View File

@@ -12,23 +12,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
# Build stage to build Go code
###############################################################################
FROM golang:1.9 as builder
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
COPY cmd/ ./cmd
COPY pkg/ ./pkg
COPY internal/ ./internal
COPY vendor/ ./vendor
RUN go build ./cmd/runmqserver/
RUN go build ./cmd/chkmqready/
RUN go build ./cmd/chkmqhealthy/
# Run all unit tests
RUN go test -v ./cmd/... ./internal/...
# Build stage to run Go unit tests
FROM golang:1.9 as tester
COPY pkg/ ./pkg
RUN cd pkg/name && go test
RUN cd pkg/linux/capabilities && go test
###############################################################################
# Main build stage, to build MQ image
###############################################################################
FROM ubuntu:16.04
# The URL to download the MQ installer from in tar.gz format

View File

@@ -16,9 +16,9 @@
FROM golang:1.9 as builder
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
COPY cmd/ ./cmd
COPY pkg/ ./pkg
COPY internal/ ./internal
COPY vendor/ ./vendor
RUN go test -c -covermode=count ./cmd/runmqserver
RUN go test -c -covermode=count -coverpkg $(go list ./cmd/runmqserver ./internal/... | paste -s -d, -) ./cmd/runmqserver
FROM mq-advancedserver:latest-x86_64

View File

@@ -13,9 +13,7 @@
# limitations under the License.
BUILD_SERVER_CONTAINER=build-server
# Set architecture for Go code. Don't set GOOS globally, so that tests can be run locally
export GOARCH ?= amd64
DOCKER_TAG_ARCH ?= x86_64
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
@@ -24,7 +22,22 @@ DOCKER_REPO_DEVSERVER ?= mq-devserver
DOCKER_REPO_ADVANCEDSERVER ?= mq-advancedserver
DOCKER_FULL_DEVSERVER = $(DOCKER_REPO_DEVSERVER):$(DOCKER_TAG)
DOCKER_FULL_ADVANCEDSERVER = $(DOCKER_REPO_ADVANCEDSERVER):$(DOCKER_TAG)
# MQ_PACKAGES is the list of MQ packages to install
MQ_PACKAGES ?=ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams
# MQ_VERSION is the fully qualified MQ version number to build
MQ_VERSION ?= 9.0.4.0
# Archive names for IBM MQ Continuous Delivery Release for Ubuntu
MQ_ARCHIVE_9.0.3.0_ppc64le=CNJR5ML.tar.gz
MQ_ARCHIVE_9.0.3.0_s390x=CNJR6ML.tar.gz
MQ_ARCHIVE_9.0.3.0_x86_64=CNJR7ML.tar.gz
MQ_ARCHIVE_9.0.4.0_ppc64le=CNLE2ML.tar.gz
MQ_ARCHIVE_9.0.4.0_s390x=CNLE3ML.tar.gz
MQ_ARCHIVE_9.0.4.0_x86_64=CNLE4ML.tar.gz
# Archive names for IBM MQ Advanced for Developers for Ubuntu
MQ_ARCHIVE_DEV_9.0.3.0=mqadv_dev903_ubuntu_x86-64.tar.gz
MQ_ARCHIVE_DEV_9.0.4.0=mqadv_dev904_ubuntu_x86-64.tar.gz
MQ_ARCHIVE ?= $(MQ_ARCHIVE_$(MQ_VERSION)_$(DOCKER_TAG_ARCH))
MQ_ARCHIVE_DEV=$(MQ_ARCHIVE_DEV_$(MQ_VERSION))
# Options to `go test` for the Docker tests
TEST_OPTS_DOCKER ?=
# Options to `go test` for the Kubernetes tests
@@ -51,13 +64,13 @@ clean:
rm -rf ./build
rm -rf ./deps
downloads/mqadv_dev903_ubuntu_x86-64.tar.gz:
downloads/$(MQ_ARCHIVE_DEV):
$(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers"$(END)))
mkdir -p downloads
cd downloads; curl -LO https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev903_ubuntu_x86-64.tar.gz
cd downloads; curl -LO https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/$(MQ_ARCHIVE_DEV)
.PHONY: downloads
downloads: downloads/mqadv_dev903_ubuntu_x86-64.tar.gz
downloads: downloads/$(MQ_ARCHIVE_DEV)
.PHONY: deps
deps:
@@ -72,18 +85,24 @@ build-cov:
.PHONY: test-advancedserver
test-advancedserver:
cd pkg/name && go test
$(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)
.PHONY: test-devserver
test-devserver:
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_FULL_DEVSERVER)"$(END)))
cd pkg/name && go test
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_FULL_DEVSERVER) on Docker"$(END)))
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_DEVSERVER) go test
.PHONY: test-advancedserver-cover
test-advancedserver-cover:
cd pkg/name && go test
$(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'
go list -f '{{.Name}}' ./internal/... | xargs -I {} go test -cover -covermode count -coverprofile ./coverage/unit-{}.cov ./internal/{}
echo 'mode: count' > ./coverage/unit.cov
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.*
cd test/docker && TEST_IMAGE=$(DOCKER_REPO_ADVANCEDSERVER):cover go test $(TEST_OPTS_DOCKER)
@@ -91,6 +110,10 @@ test-advancedserver-cover:
tail -q -n +2 ./test/docker/coverage/*.cov >> ./coverage/docker.cov
go tool cover -html=./coverage/docker.cov -o ./coverage/docker.html
echo 'mode: count' > ./coverage/combined.cov
tail -q -n +2 ./coverage/unit.cov ./coverage/docker.cov >> ./coverage/combined.cov
go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html
.PHONY: test-kubernetes-devserver
test-kubernetes-devserver:
$(call test-kubernetes,$(DOCKER_REPO_DEVSERVER),$(DOCKER_TAG),"../../charts/ibm-mqadvanced-server-dev")
@@ -127,38 +150,24 @@ define docker-build-mq
--label IBM_PRODUCT_NAME=$5 \
--label IBM_PRODUCT_VERSION=$6 \
--build-arg MQ_PACKAGES="$(MQ_PACKAGES)" \
.
# Stop the web server (will also remove the container)
$(DOCKER) kill $(BUILD_SERVER_CONTAINER)
# Delete the temporary network
$(DOCKER) network rm build
. ; $(DOCKER) kill $(BUILD_SERVER_CONTAINER) && $(DOCKER) network rm build
endef
# .PHONY: build-advancedserver-903
# build-advancedserver-903: build downloads/CNJR7ML.tar.gz
# $(info $(SPACER)$(shell printf $(TITLE)"Build $(DOCKER_FULL_ADVANCEDSERVER)"$(END)))
# $(call docker-build-mq,$(DOCKER_FULL_ADVANCEDSERVER),Dockerfile-server,CNJR7ML.tar.gz,"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced","9.0.3")
# $(DOCKER) tag $(DOCKER_FULL_ADVANCEDSERVER) $(DOCKER_REPO_ADVANCEDSERVER):9.0.3-$(DOCKER_TAG_ARCH)
.PHONY: build-advancedserver-904
build-advancedserver-904: downloads/CNLE4ML.tar.gz
$(info $(SPACER)$(shell printf $(TITLE)"Build $(DOCKER_FULL_ADVANCEDSERVER)"$(END)))
$(call docker-build-mq,$(DOCKER_FULL_ADVANCEDSERVER),Dockerfile-server,CNLE4ML.tar.gz,"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced","9.0.4")
$(DOCKER) tag $(DOCKER_FULL_ADVANCEDSERVER) $(DOCKER_REPO_ADVANCEDSERVER):9.0.4-$(DOCKER_TAG_ARCH)
.PHONY: build-advancedserver
build-advancedserver: build-advancedserver-904
build-advancedserver: downloads/$(MQ_ARCHIVE)
$(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/mqadv_dev903_ubuntu_x86-64.tar.gz
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
$(info $(shell printf $(TITLE)"Build $(DOCKER_FULL_DEVSERVER)"$(END)))
$(call docker-build-mq,$(DOCKER_FULL_DEVSERVER),Dockerfile-server,mqadv_dev903_ubuntu_x86-64.tar.gz,"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)","9.0.3")
$(DOCKER) tag $(DOCKER_FULL_DEVSERVER) $(DOCKER_REPO_DEVSERVER):9.0.3-$(DOCKER_TAG_ARCH)
# .PHONY: build-server
# build-server: build downloads/CNJR7ML.tar.gz
# $(call docker-build-mq,mq-server:latest-$(DOCKER_TAG_ARCH),Dockerfile-server,"79afd716d55b4f149a87bec52c9dc1aa","IBM MQ","9.0.3")
# $(DOCKER) tag mq-server:latest-$(DOCKER_TAG_ARCH) mq-server:9.0.3-$(DOCKER_TAG_ARCH)
$(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:
@@ -169,7 +178,7 @@ build-advancedserver-cover:
# $(call docker-build-mq,mq-web:latest-$(DOCKER_TAG_ARCH),Dockerfile-mq-web)
.PHONY: build-explorer
build-explorer: downloads/mqadv_dev903_ubuntu_x86-64.tar.gz
$(call docker-build-mq,mq-explorer:latest-$(DOCKER_TAG_ARCH),incubating/mq-explorer/Dockerfile-mq-explorer,mqadv_dev903_ubuntu_x86-64.tar.gz,"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)","9.0.3")
build-explorer: downloads/$(MQ_ARCHIVE_DEV)
$(call docker-build-mq,mq-explorer:latest-$(DOCKER_TAG_ARCH),incubating/mq-explorer/Dockerfile-mq-explorer,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
include formatting.mk

View File

@@ -22,7 +22,7 @@ import (
"os/exec"
"strings"
"github.com/ibm-messaging/mq-container/pkg/name"
"github.com/ibm-messaging/mq-container/internal/name"
)
func queueManagerHealthy() (bool, error) {

View File

@@ -0,0 +1,91 @@
/*
© 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 (
"errors"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
// resolveLicenseFile returns the file name of the MQ license file, taking into
// account the language set by the LANG environment variable
func resolveLicenseFile() string {
lang, ok := os.LookupEnv("LANG")
if !ok {
return "English.txt"
}
switch {
case strings.HasPrefix(lang, "zh_TW"):
return "Chinese_TW.txt"
case strings.HasPrefix(lang, "zh"):
return "Chinese.txt"
case strings.HasPrefix(lang, "cs"):
return "Czech.txt"
case strings.HasPrefix(lang, "fr"):
return "French.txt"
case strings.HasPrefix(lang, "de"):
return "German.txt"
case strings.HasPrefix(lang, "el"):
return "Greek.txt"
case strings.HasPrefix(lang, "id"):
return "Indonesian.txt"
case strings.HasPrefix(lang, "it"):
return "Italian.txt"
case strings.HasPrefix(lang, "ja"):
return "Japanese.txt"
case strings.HasPrefix(lang, "ko"):
return "Korean.txt"
case strings.HasPrefix(lang, "lt"):
return "Lithuanian.txt"
case strings.HasPrefix(lang, "pl"):
return "Polish.txt"
case strings.HasPrefix(lang, "pt"):
return "Portugese.txt"
case strings.HasPrefix(lang, "ru"):
return "Russian.txt"
case strings.HasPrefix(lang, "sl"):
return "Slovenian.txt"
case strings.HasPrefix(lang, "es"):
return "Spanish.txt"
case strings.HasPrefix(lang, "tr"):
return "Turkish.txt"
}
return "English.txt"
}
func checkLicense() (bool, error) {
lic, ok := os.LookupEnv("LICENSE")
switch {
case ok && lic == "accept":
return true, nil
case ok && lic == "view":
file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile())
buf, err := ioutil.ReadFile(file)
if err != nil {
log.Println(err)
return false, err
}
log.Println(string(buf))
return false, nil
}
log.Println("Error: Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions.")
log.Println("License agreements and information can be viewed by setting the environment variable LICENSE=view. You can also set the LANG environment variable to view the license in a different language.")
return false, errors.New("Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions")
}

View File

@@ -18,182 +18,73 @@ limitations under the License.
package main
import (
"fmt"
"errors"
"io/ioutil"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/name"
)
// resolveLicenseFile returns the file name of the MQ license file, taking into
// account the language set by the LANG environment variable
func resolveLicenseFile() string {
lang, ok := os.LookupEnv("LANG")
if !ok {
return "English.txt"
}
switch {
case strings.HasPrefix(lang, "zh_TW"):
return "Chinese_TW.txt"
case strings.HasPrefix(lang, "zh"):
return "Chinese.txt"
case strings.HasPrefix(lang, "cs"):
return "Czech.txt"
case strings.HasPrefix(lang, "fr"):
return "French.txt"
case strings.HasPrefix(lang, "de"):
return "German.txt"
case strings.HasPrefix(lang, "el"):
return "Greek.txt"
case strings.HasPrefix(lang, "id"):
return "Indonesian.txt"
case strings.HasPrefix(lang, "it"):
return "Italian.txt"
case strings.HasPrefix(lang, "ja"):
return "Japanese.txt"
case strings.HasPrefix(lang, "ko"):
return "Korean.txt"
case strings.HasPrefix(lang, "lt"):
return "Lithuanian.txt"
case strings.HasPrefix(lang, "pl"):
return "Polish.txt"
case strings.HasPrefix(lang, "pt"):
return "Portugese.txt"
case strings.HasPrefix(lang, "ru"):
return "Russian.txt"
case strings.HasPrefix(lang, "sl"):
return "Slovenian.txt"
case strings.HasPrefix(lang, "es"):
return "Spanish.txt"
case strings.HasPrefix(lang, "tr"):
return "Turkish.txt"
}
return "English.txt"
}
func checkLicense() {
lic, ok := os.LookupEnv("LICENSE")
switch {
case ok && lic == "accept":
return
case ok && lic == "view":
file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile())
buf, err := ioutil.ReadFile(file)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(buf))
os.Exit(1)
}
fmt.Println("Error: Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions.")
fmt.Println("License agreements and information can be viewed by setting the environment variable LICENSE=view. You can also set the LANG environment variable to view the license in a different language.")
os.Exit(1)
}
// sanitizeQueueManagerName removes any invalid characters from a queue manager name
func sanitizeQueueManagerName(name string) string {
var re = regexp.MustCompile("[^a-zA-Z0-9._%/]")
return re.ReplaceAllString(name, "")
}
// GetQueueManagerName resolves the queue manager name to use. Resolved from
// either an environment variable, or the hostname.
func getQueueManagerName() (string, error) {
var name string
var err error
name, ok := os.LookupEnv("MQ_QMGR_NAME")
if !ok || name == "" {
name, err = os.Hostname()
if err != nil {
return "", err
}
name = sanitizeQueueManagerName(name)
}
// TODO: What if the specified env variable is an invalid name?
return name, nil
}
// runCommand runs an OS command. On Linux it waits for the command to
// complete and returns the exit status (return code).
func runCommand(name string, arg ...string) (string, int, error) {
cmd := exec.Command(name, arg...)
// Run the command and wait for completion
out, err := cmd.CombinedOutput()
if err != nil {
var rc int
// Only works on Linux
if runtime.GOOS == "linux" {
var ws unix.WaitStatus
unix.Wait4(cmd.Process.Pid, &ws, 0, nil)
rc = ws.ExitStatus()
} else {
rc = -1
}
if rc == 0 {
return string(out), rc, nil
}
return string(out), rc, err
}
return string(out), 0, nil
}
// createDirStructure creates the default MQ directory structure under /var/mqm
func createDirStructure() {
out, _, err := runCommand("/opt/mqm/bin/crtmqdir", "-f", "-s")
func createDirStructure() error {
out, _, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-s")
if err != nil {
log.Fatalf("Error creating directory structure: %v\n", string(out))
log.Printf("Error creating directory structure: %v\n", string(out))
return err
}
log.Println("Created directory structure under /var/mqm")
return nil
}
func createQueueManager(name string) {
func createQueueManager(name string) error {
log.Printf("Creating queue manager %v", name)
out, rc, err := runCommand("crtmqm", "-q", "-p", "1414", name)
out, rc, err := command.Run("crtmqm", "-q", "-p", "1414", name)
if err != nil {
// 8=Queue manager exists, which is fine
if rc != 8 {
log.Printf("crtmqm returned %v", rc)
log.Fatalln(string(out))
} else {
log.Printf("Detected existing queue manager %v", name)
return
log.Println(string(out))
return err
}
log.Printf("Detected existing queue manager %v", name)
}
return nil
}
func updateCommandLevel() {
func updateCommandLevel() error {
level, ok := os.LookupEnv("MQ_CMDLEVEL")
if ok && level != "" {
out, rc, err := runCommand("strmqm", "-e", "CMDLEVEL="+level)
out, rc, err := command.Run("strmqm", "-e", "CMDLEVEL="+level)
if err != nil {
log.Fatalf("Error %v setting CMDLEVEL: %v", rc, string(out))
log.Printf("Error %v setting CMDLEVEL: %v", rc, string(out))
return err
}
}
return nil
}
func startQueueManager() {
func startQueueManager() error {
log.Println("Starting queue manager")
out, rc, err := runCommand("strmqm")
out, rc, err := command.Run("strmqm")
if err != nil {
log.Fatalf("Error %v starting queue manager: %v", rc, string(out))
log.Printf("Error %v starting queue manager: %v", rc, string(out))
return err
}
log.Println("Started queue manager")
return nil
}
func configureQueueManager() {
func configureQueueManager() error {
const configDir string = "/etc/mqm"
files, err := ioutil.ReadDir(configDir)
if err != nil {
log.Fatal(err)
log.Println(err)
return err
}
for _, file := range files {
@@ -201,12 +92,14 @@ func configureQueueManager() {
abs := filepath.Join(configDir, file.Name())
mqsc, err := ioutil.ReadFile(abs)
if err != nil {
log.Fatal(err)
log.Println(err)
return err
}
cmd := exec.Command("runmqsc")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
log.Println(err)
return err
}
stdin.Write(mqsc)
stdin.Close()
@@ -219,78 +112,78 @@ func configureQueueManager() {
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, strings.Replace(string(out), "\n", "\n\t", -1))
}
}
return nil
}
func stopQueueManager() {
func stopQueueManager(name string) error {
log.Println("Stopping queue manager")
out, _, err := runCommand("endmqm", "-w")
out, _, err := command.Run("endmqm", "-w", name)
if err != nil {
log.Fatalf("Error stopping queue manager: %v", string(out))
log.Printf("Error stopping queue manager: %v", string(out))
return err
}
log.Println("Stopped queue manager")
return nil
}
// createTerminateChannel creates a channel which will be closed when SIGTERM
// is received.
func createTerminateChannel() chan struct{} {
done := make(chan struct{})
// Handle SIGTERM
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
go func() {
sig := <-c
log.Printf("Signal received: %v", sig)
stopQueueManager()
close(done)
}()
return done
}
// createReaperChannel creates a channel which will be used to reap zombie
// (defunct) processes. This is a responsibility of processes running
// as PID 1.
func createReaper() {
// Handle SIGCHLD
c := make(chan os.Signal, 3)
signal.Notify(c, syscall.SIGCHLD)
go func() {
for {
<-c
for {
var ws unix.WaitStatus
_, err := unix.Wait4(-1, &ws, 0, nil)
// If err indicates "no child processes" left to reap, then
// wait for next SIGCHLD signal
if err == unix.ECHILD {
break
}
}
}
}()
}
func main() {
createReaper()
checkLicense()
// Start SIGTERM handler channel
done := createTerminateChannel()
name, err := getQueueManagerName()
func doMain() error {
accepted, err := checkLicense()
if err != nil {
log.Fatalln(err)
return err
}
if !accepted {
return errors.New("License not accepted")
}
name, err := name.GetQueueManagerName()
if err != nil {
log.Println(err)
return err
}
log.Printf("Using queue manager name: %v", name)
// Start signal handler
signalControl := signalHandler(name)
logConfig()
err = createVolume("/mnt/mqm")
if err != nil {
log.Fatal(err)
log.Println(err)
return err
}
err = createDirStructure()
if err != nil {
return err
}
err = createQueueManager(name)
if err != nil {
return err
}
err = updateCommandLevel()
if err != nil {
return err
}
err = startQueueManager()
if err != nil {
return err
}
createDirStructure()
createQueueManager(name)
updateCommandLevel()
startQueueManager()
configureQueueManager()
// Start reaping zombies from now on.
// Start this here, so that we don't reap any sub-processes created
// by this process (e.g. for crtmqm or strmqm)
signalControl <- startReaping
// Reap zombies now, just in case we've already got some
signalControl <- reapNow
// Wait for terminate signal
<-done
<-signalControl
return nil
}
var osExit = os.Exit
func main() {
err := doMain()
if err != nil {
osExit(1)
}
}

View File

@@ -17,6 +17,9 @@ package main
import (
"flag"
"io/ioutil"
"log"
"strconv"
"testing"
)
@@ -29,6 +32,15 @@ func init() {
// Test started when the test binary is started. Only calls main.
func TestSystem(t *testing.T) {
if *test {
var oldExit = osExit
defer func() {
osExit = oldExit
}()
osExit = func(rc int) {
// Write the exit code to a file instead
log.Printf("Writing exit code %v to file", strconv.Itoa(rc))
ioutil.WriteFile("/var/coverage/exitCode", []byte(strconv.Itoa(rc)), 0644)
}
main()
}
}

View File

@@ -22,7 +22,7 @@ import (
"runtime"
"strings"
"github.com/ibm-messaging/mq-container/pkg/linux/capabilities"
"github.com/ibm-messaging/mq-container/internal/capabilities"
"golang.org/x/sys/unix"
)

View File

@@ -0,0 +1,79 @@
/*
© 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 (
"log"
"os"
"os/signal"
"syscall"
"golang.org/x/sys/unix"
)
const (
startReaping = iota
reapNow = iota
)
func signalHandler(qmgr string) chan int {
control := make(chan int)
// Use separate channels for the signals, to avoid SIGCHLD signals swamping
// the buffer, and preventing other signals.
stopSignals := make(chan os.Signal)
reapSignals := make(chan os.Signal)
signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT)
go func() {
for {
select {
case sig := <-stopSignals:
log.Printf("Signal received: %v", sig)
signal.Stop(reapSignals)
signal.Stop(stopSignals)
stopQueueManager(qmgr)
// One final reap
reapZombies()
close(control)
// End the goroutine
return
case <-reapSignals:
reapZombies()
case job := <-control:
switch {
case job == startReaping:
// Add SIGCHLD to the list of signals we're listening to
signal.Notify(reapSignals, syscall.SIGCHLD)
case job == reapNow:
reapZombies()
}
}
}
}()
return control
}
// reapZombies reaps any zombie (terminated) processes now.
// This function should be called before exiting.
func reapZombies() {
for {
var ws unix.WaitStatus
pid, err := unix.Wait4(-1, &ws, unix.WNOHANG, nil)
// If err or pid indicate "no child processes"
if pid == 0 || err == unix.ECHILD {
return
}
}
}

View File

@@ -8,18 +8,37 @@ You need to ensure you have the following tools installed:
* [Glide](https://glide.sh/)
* [dep](https://github.com/golang/dep) (official Go dependency management tool)
* make
* [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/).
## Building a production image
This procedure works for building the MQ Continuous Delivery release, on `x86_64`, `ppc64le` and `s390x` architectures.
1. Download MQ from IBM Passport Advantage, and place the downloaded file (for example, `CNLE4ML.tar.gz` for MQ V9.0.4 on x86_64 architecture) in the `downloads` directory
2. Run `make build-advancedserver`
You can build a different version of MQ by setting the `MQ_VERSION` environment variable, for example:
```bash
MQ_VERSION=9.0.3.0 make build-advancedserver
```
## Running the tests
There are three main sets of tests:
1. Unit tests
1. Unit tests, which are run during a build
2. Docker tests, which test a complete Docker image, using the Docker API
3. Kubernetes tests, which test the Helm charts (and the Docker image) via [Helm](https://helm.sh)
### Running the tests
The unit and Docker tests can be run locally. For example:
### Running the Docker tests
The Docker tests can be run locally. Before you run them for the first time, you need to download the test dependencies:
```
make deps
```
You can then run the tests, for example:
```
make test-devserver
@@ -31,7 +50,7 @@ or:
make test-advancedserver
```
### Running the tests with code coverage
### Running the Docker tests with code coverage
You can produce code coverage results from the Docker tests by running the following:
```

View File

@@ -0,0 +1,47 @@
/*
© 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 command contains code to run external commands
package command
import (
"os/exec"
"runtime"
"syscall"
)
// Run runs an OS command. On Linux it waits for the command to
// complete and returns the exit status (return code).
// Do not use this function to run shell built-ins (like "cd"), because
// the error handling works differently
func Run(name string, arg ...string) (string, int, error) {
cmd := exec.Command(name, arg...)
// Run the command and wait for completion
out, err := cmd.CombinedOutput()
if err != nil {
// Assert that this is an ExitError
exiterr, ok := err.(*exec.ExitError)
// If the type assertion was correct, and we're on Linux
if ok && runtime.GOOS == "linux" {
status, ok := exiterr.Sys().(syscall.WaitStatus)
if ok {
return string(out), status.ExitStatus(), err
}
}
return string(out), -1, err
}
return string(out), 0, nil
}

View File

@@ -0,0 +1,47 @@
/*
© 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 command
import (
"runtime"
"testing"
)
var commandTests = []struct {
name string
arg []string
rc int
}{
{"ls", []string{}, 0},
{"ls", []string{"madeup"}, 2},
{"bash", []string{"-c", "exit 99"}, 99},
}
func TestRun(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("Skipping tests for package which only works on Linux")
}
for _, table := range commandTests {
arg := table.arg
_, rc, err := Run(table.name, arg...)
if rc != table.rc {
t.Errorf("Run(%v,%v) - expected %v, got %v", table.name, table.arg, table.rc, rc)
}
if rc != 0 && err == nil {
t.Errorf("Run(%v,%v) - expected error for non-zero return code (rc=%v)", table.name, table.arg, rc)
}
}
}

View File

@@ -20,11 +20,9 @@ package name
import (
"os"
"regexp"
//log "github.com/sirupsen/logrus"
)
// sanitizeQueueManagerName removes any invalid characters from a queue manager name
// TODO: This is duplicate code
func sanitizeQueueManagerName(name string) string {
var re = regexp.MustCompile("[^a-zA-Z0-9._%/]")
return re.ReplaceAllString(name, "")

View File

@@ -167,3 +167,55 @@ func TestNoVolumeWithRestart(t *testing.T) {
startContainer(t, cli, id)
waitForReady(t, cli, id)
}
// Test the case where `crtmqm` will fail
func TestCreateQueueManagerFail(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
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},
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 10)
if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc)
}
}
// Test the case where `strmqm` will fail
func TestStartQueueManagerFail(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
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},
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 10)
if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc)
}
}

View File

@@ -22,6 +22,7 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"testing"
"time"
@@ -61,13 +62,15 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
// 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")
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)
if err != nil {
// Just log the error and continue
t.Log(err)
}
t.Log("Container stopped")
// If a code coverage file has been generated, then rename it to match the test name
os.Rename(filepath.Join(coverageDir(t), "container.cov"), filepath.Join(coverageDir(t), t.Name()+".cov"))
// Log the container output for any container we're about to delete
@@ -134,11 +137,40 @@ func stopContainer(t *testing.T, cli *client.Client, ID string) {
}
}
func getCoverageExitCode(t *testing.T, orig int64) int64 {
f := filepath.Join(coverageDir(t), "exitCode")
_, err := os.Stat(f)
if err != nil {
t.Log(err)
return orig
}
// Remove the file, ready for the next test
//defer os.Remove(f)
buf, err := ioutil.ReadFile(f)
if err != nil {
t.Log(err)
return orig
}
rc, err := strconv.Atoi(string(buf))
if err != nil {
t.Log(err)
return orig
}
t.Logf("Retrieved exit code %v from file", rc)
return int64(rc)
}
// 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)
// COVERAGE: When running coverage, the exit code is written to a file,
// to allow the coverage to be generated (which doesn't happen for non-zero
// exit codes)
rc = getCoverageExitCode(t, rc)
// err := <-errC
if err != nil {
t.Fatal(err)