Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ef3bfa28d | ||
|
|
401306b3ab | ||
|
|
e809d0959b | ||
|
|
ef4232dee5 | ||
|
|
37b650c367 | ||
|
|
4fc5460ac8 | ||
|
|
01331b0203 | ||
|
|
b240a84ce0 | ||
|
|
c75aed0851 | ||
|
|
d495b3640e | ||
|
|
98c594c91e | ||
|
|
6b4720a07c | ||
|
|
10e448056b | ||
|
|
1b350549cc | ||
|
|
104098c7b4 | ||
|
|
a14da7aaa8 | ||
|
|
ab60a8975f | ||
|
|
9c072060df | ||
|
|
6f8b47a670 | ||
|
|
8710721fe3 | ||
|
|
37e07fc0b0 | ||
|
|
97f5f44b92 | ||
|
|
e07110108f | ||
|
|
1e0ba3d897 | ||
|
|
782fe367a5 | ||
|
|
e1829f3f47 | ||
|
|
91bf65ab57 | ||
|
|
b497a04dcb | ||
|
|
c9cc1741c7 | ||
|
|
d70bbe4dfa | ||
|
|
cb475e8dde | ||
|
|
c30a8e4223 | ||
|
|
02cce7ab96 | ||
|
|
8c92e02a23 | ||
|
|
0845025cf8 | ||
|
|
3600841e2b | ||
|
|
05d1c6f0de | ||
|
|
ace6e6364c | ||
|
|
b23ec084fa | ||
|
|
e65d679a21 | ||
|
|
b62a883d4c | ||
|
|
9b8e32cbf0 | ||
|
|
4cdf03a77c | ||
|
|
2312842a9f | ||
|
|
911f9db2fd | ||
|
|
54a5052631 | ||
|
|
1d223f3157 | ||
|
|
cf687196b7 | ||
|
|
47c865d497 | ||
|
|
40c7ab927e | ||
|
|
3f4dd2dd3a | ||
|
|
b24e375912 | ||
|
|
48185668af | ||
|
|
cbfa24a267 | ||
|
|
0459880a65 | ||
|
|
741aa18da9 | ||
|
|
eab783f3c5 | ||
|
|
a03bad9ee7 | ||
|
|
81bba0db32 | ||
|
|
753612530f | ||
|
|
f565e9fe66 | ||
|
|
ce40e207a4 | ||
|
|
ed1154c881 | ||
|
|
d1852d9af4 | ||
|
|
0eacad5848 | ||
|
|
bf4cdfc906 | ||
|
|
238529918c | ||
|
|
20b54f35ce | ||
|
|
bf31513b99 | ||
|
|
451ea00aed | ||
|
|
095009dba0 | ||
|
|
fe23ec9aaa | ||
|
|
45f53082aa | ||
|
|
f6c583df0b | ||
|
|
ee94b1702a | ||
|
|
64867944a3 | ||
|
|
b938a5eb95 | ||
|
|
b6b72952e4 | ||
|
|
aeefd0a9fc | ||
|
|
a2326dc0f6 | ||
|
|
93389309f4 | ||
|
|
d6182bf2fc | ||
|
|
499c6d4b18 | ||
|
|
18635b704c | ||
|
|
878442905d | ||
|
|
b66a799d7f | ||
|
|
584a4f2eb4 | ||
|
|
c297e1bf5d | ||
|
|
abbc8ce852 | ||
|
|
176543e100 | ||
|
|
f5515d72a3 | ||
|
|
70f1a43fd8 | ||
|
|
81b5db4969 | ||
|
|
2f3ca70d7b | ||
|
|
a80b839c14 | ||
|
|
5b0259ec6e | ||
|
|
0ad595dc99 | ||
|
|
0823fd1cea | ||
|
|
f3a1f94445 | ||
|
|
5780ec2a26 | ||
|
|
7ea6b4c610 | ||
|
|
4aa8918745 | ||
|
|
4200d9df1b | ||
|
|
f492171276 | ||
|
|
d674334574 | ||
|
|
fef9ab4f06 | ||
|
|
46110436b8 | ||
|
|
5e787ba4cf | ||
|
|
099397442b | ||
|
|
3848c39147 | ||
|
|
3bbdeec3f0 | ||
|
|
faf6c4bc08 | ||
|
|
15acd8ccdc | ||
|
|
fceac2d4b8 | ||
|
|
3da06b27a5 | ||
|
|
f65b06be3c | ||
|
|
35043ee488 | ||
|
|
4874483f9c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
||||||
test/docker/coverage
|
test/docker/coverage
|
||||||
test/docker/vendor
|
test/docker/vendor
|
||||||
test/kubernetes/vendor
|
test/kubernetes/vendor
|
||||||
|
|||||||
10
.travis.yml
10
.travis.yml
@@ -13,17 +13,13 @@ before_install:
|
|||||||
- sudo apt-get update
|
- sudo apt-get update
|
||||||
- sudo apt-get -y install docker-ce
|
- sudo apt-get -y install docker-ce
|
||||||
- curl https://glide.sh/get | sh
|
- curl https://glide.sh/get | sh
|
||||||
- curl -LO https://github.com/golang/dep/releases/download/v0.3.0/dep-linux-amd64.zip
|
- sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64
|
||||||
- unzip dep-linux-amd64.zip
|
- sudo chmod +x /usr/local/bin/dep
|
||||||
- sudo mv dep /usr/local/bin
|
|
||||||
- rm dep-linux-amd64.zip
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- echo nothing
|
- echo nothing
|
||||||
|
|
||||||
before_script:
|
|
||||||
- make deps
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- make deps
|
||||||
- make build-devserver
|
- make build-devserver
|
||||||
- make test-devserver
|
- make test-devserver
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Change log
|
# Change log
|
||||||
|
|
||||||
|
## master
|
||||||
|
* Container's stdout can now be set to JSON format (set LOG_FORMAT=json)
|
||||||
|
* MQ error logs (in JSON or plain text) are now mirrored on stdout for the container.
|
||||||
|
* `chkmqready` now waits until MQSC scripts in `/etc/mqm` have been applied
|
||||||
|
* `chkmqready` and `chkmqhealthy` now run as the "mqm" user
|
||||||
|
* Added ability to optionally use an alternative base image
|
||||||
|
* Various build and test improvements
|
||||||
|
|
||||||
## 9.0.4 (2017-11-06)
|
## 9.0.4 (2017-11-06)
|
||||||
* Updated to MQ version 9.0.4.0
|
* Updated to MQ version 9.0.4.0
|
||||||
* Updated to Go version 9
|
* Updated to Go version 9
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# © Copyright IBM Corporation 2015, 2017
|
# © Copyright IBM Corporation 2015, 2018
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,31 +12,36 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
ARG BASE_IMAGE=ubuntu:16.04
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
# Build stage to build Go code
|
# Build stage to build Go code
|
||||||
|
###############################################################################
|
||||||
FROM golang:1.9 as builder
|
FROM golang:1.9 as builder
|
||||||
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
|
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
|
||||||
COPY cmd/ ./cmd
|
COPY cmd/ ./cmd
|
||||||
COPY pkg/ ./pkg
|
COPY internal/ ./internal
|
||||||
COPY vendor/ ./vendor
|
COPY vendor/ ./vendor
|
||||||
RUN go build ./cmd/runmqserver/
|
RUN go build ./cmd/runmqserver/
|
||||||
RUN go build ./cmd/chkmqready/
|
RUN go build ./cmd/chkmqready/
|
||||||
RUN go build ./cmd/chkmqhealthy/
|
RUN go build ./cmd/chkmqhealthy/
|
||||||
|
# Run all unit tests
|
||||||
|
RUN go test -v ./cmd/runmqserver/
|
||||||
|
RUN go test -v ./cmd/chkmqready/
|
||||||
|
RUN go test -v ./cmd/chkmqhealthy/
|
||||||
|
RUN go test -v ./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
|
# Main build stage, to build MQ image
|
||||||
FROM ubuntu:16.04
|
###############################################################################
|
||||||
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
# The URL to download the MQ installer from in tar.gz format
|
# The URL to download the MQ installer from in tar.gz format
|
||||||
# This assumes an archive containing the MQ Debian (.deb) install packages
|
# This assumes an archive containing the MQ Debian (.deb) install packages
|
||||||
ARG MQ_URL
|
ARG MQ_URL
|
||||||
|
|
||||||
# The MQ packages to install
|
# The MQ packages to install - see install-mq.sh for default value
|
||||||
ARG MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams"
|
ARG MQ_PACKAGES
|
||||||
|
|
||||||
COPY install-mq.sh /usr/local/bin/
|
COPY install-mq.sh /usr/local/bin/
|
||||||
|
|
||||||
@@ -45,16 +50,21 @@ RUN chmod u+x /usr/local/bin/install-mq.sh \
|
|||||||
&& sleep 1 \
|
&& sleep 1 \
|
||||||
&& install-mq.sh
|
&& install-mq.sh
|
||||||
|
|
||||||
|
# Create a directory for runtime data from runmqserver
|
||||||
|
RUN mkdir -p /run/runmqserver \
|
||||||
|
&& chown mqm:mqm /run/runmqserver
|
||||||
|
|
||||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
||||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
|
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
|
||||||
COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt
|
COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/runmqserver \
|
RUN chmod ug+x /usr/local/bin/runmqserver \
|
||||||
&& chmod +x /usr/local/bin/chkmq*
|
&& chown mqm:mqm /usr/local/bin/*mq* \
|
||||||
|
&& chmod ug+xs /usr/local/bin/chkmq*
|
||||||
|
|
||||||
# Always use port 1414
|
# Always use port 1414
|
||||||
EXPOSE 1414
|
EXPOSE 1414
|
||||||
|
|
||||||
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1
|
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 LOG_FORMAT=basic
|
||||||
|
|
||||||
ENTRYPOINT ["runmqserver"]
|
ENTRYPOINT ["runmqserver"]
|
||||||
|
|||||||
@@ -12,11 +12,21 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
FROM mqadvanced:latest-x86_64
|
ARG BASE_IMAGE
|
||||||
|
|
||||||
|
# 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 internal/ ./internal
|
||||||
|
COPY vendor/ ./vendor
|
||||||
|
RUN go test -c -covermode=count -coverpkg $(go list ./cmd/runmqserver ./internal/... | paste -s -d, -) ./cmd/runmqserver
|
||||||
|
|
||||||
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
# Copy in the version of the code instrumented for code coverage
|
# Copy in the version of the code instrumented for code coverage
|
||||||
COPY build/runmqserver.test /usr/local/bin/runmqserver.test
|
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver.test /usr/local/bin/
|
||||||
RUN chmod +x /usr/local/bin/runmqserver.test \
|
RUN chmod +x /usr/local/bin/runmqserver.test \
|
||||||
&& mkdir -p /var/coverage/
|
&& mkdir -p /var/coverage/
|
||||||
|
|
||||||
ENTRYPOINT ["runmqserver.test", "-test", "-test.coverprofile", "/var/coverage/coverage.cov"]
|
ENTRYPOINT ["runmqserver.test", "-test", "-test.coverprofile", "/var/coverage/container.cov"]
|
||||||
|
|||||||
205
Makefile
205
Makefile
@@ -1,4 +1,4 @@
|
|||||||
# © Copyright IBM Corporation 2017
|
# © Copyright IBM Corporation 2017, 2018
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,22 +12,75 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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
|
# Conditional variables - you can override the values of these variables from
|
||||||
export GOARCH ?= amd64
|
# the command line
|
||||||
DOCKER_TAG_ARCH ?= x86_64
|
###############################################################################
|
||||||
# By default, all Docker client commands are run inside a Docker container.
|
# BASE_IMAGE is the base image to use for MQ, for example "ubuntu" or "rhel"
|
||||||
# This means that newer features of the client can be used, even with an older daemon.
|
BASE_IMAGE ?= ubuntu:16.04
|
||||||
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
|
# MQ_VERSION is the fully qualified MQ version number to build
|
||||||
DOCKER_TAG ?= latest-$(DOCKER_TAG_ARCH)
|
MQ_VERSION ?= 9.0.4.0
|
||||||
DOCKER_REPO_DEVSERVER ?= mq-devserver
|
# MQ_ARCHIVE is the name of the file, under the downloads directory, from which MQ Advanced can
|
||||||
DOCKER_REPO_ADVANCEDSERVER ?= mq-advancedserver
|
# be installed. The default value is derived from MQ_VERSION, BASE_IMAGE and architecture
|
||||||
DOCKER_FULL_DEVSERVER = $(DOCKER_REPO_DEVSERVER):$(DOCKER_TAG)
|
# Does not apply to MQ Advanced for Developers.
|
||||||
DOCKER_FULL_ADVANCEDSERVER = $(DOCKER_REPO_ADVANCEDSERVER):$(DOCKER_TAG)
|
MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH).tar.gz
|
||||||
|
# MQ_ARCHIVE_DEV is the name of the file, under the downloads directory, from which MQ Advanced
|
||||||
|
# for Developers can be installed
|
||||||
|
MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
|
||||||
# Options to `go test` for the Docker tests
|
# Options to `go test` for the Docker tests
|
||||||
TEST_OPTS_DOCKER ?=
|
TEST_OPTS_DOCKER ?=
|
||||||
# Options to `go test` for the Kubernetes tests
|
# Options to `go test` for the Kubernetes tests
|
||||||
TEST_OPTS_KUBERNETES ?=
|
TEST_OPTS_KUBERNETES ?=
|
||||||
|
# MQ_IMAGE_ADVANCEDSERVER is the name and tag of the built MQ Advanced image
|
||||||
|
MQ_IMAGE_ADVANCEDSERVER ?=mqadvanced-server:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||||
|
# MQ_IMAGE_DEVSERVER is the name and tag of the built MQ Advanced for Developers image
|
||||||
|
MQ_IMAGE_DEVSERVER ?=mqadvanced-server-dev:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||||
|
# DOCKER is the Docker command to run
|
||||||
|
DOCKER ?= docker
|
||||||
|
# MQ_PACKAGES specifies the MQ packages (.deb or .rpm) to install. Defaults vary on base image.
|
||||||
|
MQ_PACKAGES ?=
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Other variables
|
||||||
|
###############################################################################
|
||||||
|
# ARCH is the platform architecture (e.g. x86_64, ppc64le or s390x)
|
||||||
|
ARCH = $(shell uname -m)
|
||||||
|
# BUILD_SERVER_CONTAINER is the name of the web server container used at build time
|
||||||
|
BUILD_SERVER_CONTAINER=build-server
|
||||||
|
# NUM_CPU is the number of CPUs available to Docker. Used to control how many
|
||||||
|
# test run in parallel
|
||||||
|
NUM_CPU=$(shell docker info --format "{{ .NCPU }}")
|
||||||
|
# BASE_IMAGE_TAG is a normalized version of BASE_IMAGE, suitable for use in a Docker tag
|
||||||
|
BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE)))
|
||||||
|
MQ_IMAGE_DEVSERVER_BASE=mqadvanced-server-dev-base:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||||
|
|
||||||
|
# Try to figure out which archive to use from the BASE_IMAGE
|
||||||
|
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
|
||||||
|
MQ_ARCHIVE_TYPE=UBUNTU
|
||||||
|
else
|
||||||
|
MQ_ARCHIVE_TYPE=LINUX
|
||||||
|
endif
|
||||||
|
# Try to figure out which archive to use from the architecture
|
||||||
|
ifeq "$(ARCH)" "x86_64"
|
||||||
|
MQ_ARCHIVE_ARCH=X86-64
|
||||||
|
else ifeq "$(ARCH)" "ppc64le"
|
||||||
|
MQ_ARCHIVE_ARCH=LE_POWER
|
||||||
|
else ifeq "$(ARCH)" "s390x"
|
||||||
|
MQ_ARCHIVE_ARCH=SYSTEM_Z
|
||||||
|
endif
|
||||||
|
# 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
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Build targets
|
||||||
|
###############################################################################
|
||||||
|
.PHONY: vars
|
||||||
|
vars:
|
||||||
|
#ifeq "$(findstring ubuntu,$(BASE_IMAGE))","ubuntu"
|
||||||
|
@echo $(MQ_ARCHIVE_ARCH)
|
||||||
|
@echo $(MQ_ARCHIVE_TYPE)
|
||||||
|
@echo $(MQ_ARCHIVE)
|
||||||
|
|
||||||
.PHONY: default
|
.PHONY: default
|
||||||
default: build-devserver test
|
default: build-devserver test
|
||||||
@@ -49,47 +102,80 @@ clean:
|
|||||||
rm -rf ./build
|
rm -rf ./build
|
||||||
rm -rf ./deps
|
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)))
|
$(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers"$(END)))
|
||||||
mkdir -p downloads
|
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
|
.PHONY: downloads
|
||||||
downloads: downloads/mqadv_dev903_ubuntu_x86-64.tar.gz
|
downloads: downloads/$(MQ_ARCHIVE_DEV)
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps:
|
deps:
|
||||||
glide install --strip-vendor
|
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/docker && dep ensure -vendor-only
|
||||||
cd test/kubernetes && dep ensure -vendor-only
|
|
||||||
|
|
||||||
.PHONY: build-cov
|
.PHONY: build-cov
|
||||||
build-cov:
|
build-cov:
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
cd build; go test -c -covermode=count ../cmd/runmqserver
|
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
|
.PHONY: test-advancedserver
|
||||||
test-advancedserver:
|
test-advancedserver: test/docker/vendor
|
||||||
cd pkg/name && go test
|
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER) on Docker"$(END)))
|
||||||
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_ADVANCEDSERVER) go test $(TEST_OPTS_DOCKER)
|
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
|
||||||
|
|
||||||
.PHONY: test-devserver
|
.PHONY: test-devserver
|
||||||
test-devserver:
|
test-devserver: test/docker/vendor
|
||||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(DOCKER_FULL_DEVSERVER)"$(END)))
|
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER) on Docker"$(END)))
|
||||||
cd pkg/name && go test
|
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER) go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER)
|
||||||
cd test/docker && TEST_IMAGE=$(DOCKER_FULL_DEVSERVER) go test
|
|
||||||
|
.PHONY: test-advancedserver-cover
|
||||||
|
test-advancedserver-cover: test/docker/vendor
|
||||||
|
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_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/{}
|
||||||
|
# ls -1 ./cmd | xargs -I {} go test -cover -covermode count -coverprofile ./coverage/unit-{}.cov ./cmd/{}/...
|
||||||
|
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.*
|
||||||
|
mkdir -p ./test/docker/coverage/
|
||||||
|
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER)-cover TEST_COVER=true 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
|
||||||
|
|
||||||
|
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
|
.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")
|
$(call test-kubernetes,$(MQ_IMAGE_DEVSERVER),"../../charts/ibm-mqadvanced-server-dev")
|
||||||
|
|
||||||
.PHONY: test-kubernetes-advancedserver
|
.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")
|
$(call test-kubernetes,$(MQ_IMAGE_ADVANCEDSERVER),"../../charts/ibm-mqadvanced-server-prod")
|
||||||
|
|
||||||
define test-kubernetes
|
define test-kubernetes
|
||||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $1:$2 on Kubernetes"$(END)))
|
$(info $(SPACER)$(shell printf $(TITLE)"Test $1 on Kubernetes"$(END)))
|
||||||
cd test/kubernetes && TEST_REPO=$1 TEST_TAG=$2 TEST_CHART=$3 go test $(TEST_OPTS_KUBERNETES)
|
cd test/kubernetes && TEST_IMAGE=$1 TEST_CHART=$2 go test $(TEST_OPTS_KUBERNETES)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define docker-build-mq
|
define docker-build-mq
|
||||||
@@ -104,59 +190,48 @@ define docker-build-mq
|
|||||||
--volume "$(realpath ./downloads/)":/usr/share/nginx/html:ro \
|
--volume "$(realpath ./downloads/)":/usr/share/nginx/html:ro \
|
||||||
--detach \
|
--detach \
|
||||||
nginx:alpine
|
nginx:alpine
|
||||||
# Build the new image
|
# Build the new image (use --pull to make sure we have the latest base image)
|
||||||
$(DOCKER) build \
|
$(DOCKER) build \
|
||||||
--pull \
|
--pull \
|
||||||
--tag $1 \
|
--tag $1 \
|
||||||
--file $2 \
|
--file $2 \
|
||||||
--network build \
|
--network build \
|
||||||
--build-arg MQ_URL=http://build:80/$3 \
|
--build-arg MQ_URL=http://build:80/$3 \
|
||||||
|
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
|
||||||
--label IBM_PRODUCT_ID=$4 \
|
--label IBM_PRODUCT_ID=$4 \
|
||||||
--label IBM_PRODUCT_NAME=$5 \
|
--label IBM_PRODUCT_NAME=$5 \
|
||||||
--label IBM_PRODUCT_VERSION=$6 \
|
--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) && $(DOCKER) network rm build
|
||||||
$(DOCKER) kill $(BUILD_SERVER_CONTAINER)
|
|
||||||
# Delete the temporary network
|
|
||||||
$(DOCKER) network rm build
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
# .PHONY: build-advancedserver-903
|
DOCKER_SERVER_VERSION=$(shell docker version --format "{{ .Server.Version }}")
|
||||||
# build-advancedserver-903: build downloads/CNJR7ML.tar.gz
|
DOCKER_CLIENT_VERSION=$(shell docker version --format "{{ .Client.Version }}")
|
||||||
# $(info $(SPACER)$(shell printf $(TITLE)"Build $(DOCKER_FULL_ADVANCEDSERVER)"$(END)))
|
.PHONY: docker-version
|
||||||
# $(call docker-build-mq,$(DOCKER_FULL_ADVANCEDSERVER),Dockerfile-server,CNJR7ML.tar.gz,"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced","9.0.3")
|
docker-version:
|
||||||
# $(DOCKER) tag $(DOCKER_FULL_ADVANCEDSERVER) $(DOCKER_REPO_ADVANCEDSERVER):9.0.3-$(DOCKER_TAG_ARCH)
|
@test "$(word 1,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "17" || ("$(word 1,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -eq "17" && "$(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" || ("$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -eq "17" && "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05") || (echo "Error: Docker server 17.05 or greater is required" && exit 1)
|
||||||
.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
|
.PHONY: build-advancedserver
|
||||||
build-advancedserver: build-advancedserver-904
|
build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version
|
||||||
|
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER)"$(END)))
|
||||||
|
$(call docker-build-mq,$(MQ_IMAGE_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION))
|
||||||
|
|
||||||
.PHONY: build-devserver
|
.PHONY: build-devserver
|
||||||
build-devserver: downloads/mqadv_dev903_ubuntu_x86-64.tar.gz
|
# Target-specific variable to add web server into devserver image
|
||||||
$(info $(shell printf $(TITLE)"Build $(DOCKER_FULL_DEVSERVER)"$(END)))
|
build-devserver: MQ_PACKAGES=ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams ibmmq-web
|
||||||
$(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")
|
build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version
|
||||||
$(DOCKER) tag $(DOCKER_FULL_DEVSERVER) $(DOCKER_REPO_DEVSERVER):9.0.3-$(DOCKER_TAG_ARCH)
|
@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 $(MQ_IMAGE_DEVSERVER_BASE)"$(END)))
|
||||||
# .PHONY: build-server
|
$(call docker-build-mq,$(MQ_IMAGE_DEVSERVER_BASE),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
|
||||||
# build-server: build downloads/CNJR7ML.tar.gz
|
docker build --tag $(MQ_IMAGE_DEVSERVER) --file incubating/mqadvanced-server-dev/Dockerfile .
|
||||||
# $(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)
|
|
||||||
|
|
||||||
.PHONY: build-advancedserver-cover
|
.PHONY: build-advancedserver-cover
|
||||||
build-advancedserver-cover: build-advanced-server build-cov
|
build-advancedserver-cover: docker-version
|
||||||
$(DOCKER) build -t mq-advancedserver:cover -f Dockerfile-server.cover .
|
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) -t $(MQ_IMAGE_ADVANCEDSERVER)-cover -f Dockerfile-server.cover .
|
||||||
|
|
||||||
# .PHONY: build-web
|
|
||||||
# build-web: build downloads/CNJR7ML.tar.gz
|
|
||||||
# $(call docker-build-mq,mq-web:latest-$(DOCKER_TAG_ARCH),Dockerfile-mq-web)
|
|
||||||
|
|
||||||
.PHONY: build-explorer
|
.PHONY: build-explorer
|
||||||
build-explorer: downloads/mqadv_dev903_ubuntu_x86-64.tar.gz
|
build-explorer: downloads/$(MQ_ARCHIVE_DEV)
|
||||||
$(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")
|
$(call docker-build-mq,mq-explorer:latest-$(ARCH),incubating/mq-explorer/Dockerfile-mq-explorer,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
|
||||||
|
|
||||||
include formatting.mk
|
include formatting.mk
|
||||||
|
|||||||
164
NOTICES.txt
164
NOTICES.txt
@@ -1,100 +1,66 @@
|
|||||||
===============================================================================
|
===============================================================================
|
||||||
THE FOLLOWING SECTIONS IDENTIFY VARIOUS COMPONENTS CONTAINED IN THE PROGRAM, AND SPECIFY CERTAIN NOTICES AND OTHER INFORMATION REGARDING THOSE COMPONENTS THAT IBM IS REQUIRED TO PROVIDE TO YOU.
|
THE FOLLOWING SECTIONS IDENTIFY VARIOUS COMPONENTS CONTAINED IN THE PROGRAM, AND SPECIFY CERTAIN NOTICES AND OTHER INFORMATION REGARDING THOSE COMPONENTS THAT IBM IS REQUIRED TO PROVIDE TO YOU.
|
||||||
|
|
||||||
NOTWITHSTANDING ANY PROVISION CONTAINED IN ANY OF THE NOTICES AND OTHER INFORMATION SET FORTH BELOW, YOUR USE OF THESE PROGRAM COMPONENTS REMAINS SUBJECT TO THE TERMS AND CONDITIONS SET FORTH IN:
|
NOTWITHSTANDING ANY PROVISION CONTAINED IN ANY OF THE NOTICES AND OTHER INFORMATION SET FORTH BELOW, YOUR USE OF THESE PROGRAM COMPONENTS REMAINS SUBJECT TO THE TERMS AND CONDITIONS SET FORTH IN:
|
||||||
|
|
||||||
(i) THE PROGRAM'S LICENSE INFORMATION DOCUMENT; AND
|
(i) THE PROGRAM'S LICENSE INFORMATION DOCUMENT; AND
|
||||||
(ii) THE IBM LICENSE AGREEMENT SPECIFIED IN THAT LICENSE INFORMATION DOCUMENT.
|
(ii) THE IBM LICENSE AGREEMENT SPECIFIED IN THAT LICENSE INFORMATION DOCUMENT.
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
A. SUMMARY
|
A. SUMMARY
|
||||||
===============================================================================
|
===============================================================================
|
||||||
Section B. below contains provisions relating to certain other components of the Program, as follows:-
|
Section B. below contains provisions relating to certain other components of the Program, as follows:-
|
||||||
|
|
||||||
B.1 MIT license
|
B.1 BSD 3-Clause license
|
||||||
Affected Components:
|
Affected Components:
|
||||||
* github.com/hpcloud/tail
|
* golang.org/x/crypto
|
||||||
Copyright 2015 Hewlett Packard Enterprise Development LP
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
Copyright (c) 2014 ActiveState
|
* golang.org/x/sys
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
B.2 BSD 3-Clause license
|
|
||||||
Affected Components:
|
===============================================================================
|
||||||
* golang.org/x/crypto
|
END OF A. SUMMARY
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
===============================================================================
|
||||||
* golang.org/x/sys
|
==========================================================================
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
B. LICENSE FILES AND OTHER INFORMATION
|
||||||
* gopkg.in/fsnotify.v1
|
==========================================================================
|
||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
==========================================================================
|
||||||
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
B.1 BSD 3-Clause license
|
||||||
* gopkg.in/tomb.v1
|
==========================================================================
|
||||||
Copyright (c) 2010-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>. All rights reserved.
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
===============================================================================
|
met:
|
||||||
END OF A. SUMMARY
|
|
||||||
===============================================================================
|
* Redistributions of source code must retain the above copyright
|
||||||
==========================================================================
|
notice, this list of conditions and the following disclaimer.
|
||||||
B. LICENSE FILES AND OTHER INFORMATION
|
* Redistributions in binary form must reproduce the above
|
||||||
==========================================================================
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
==========================================================================
|
in the documentation and/or other materials provided with the
|
||||||
B.1 MIT license
|
distribution.
|
||||||
==========================================================================
|
* Neither the name of Google Inc. nor the names of its
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
contributors may be used to endorse or promote products derived from
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
this software without specific prior written permission.
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
furnished to do so, subject to the following conditions:
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
The above copyright notice and this permission notice shall be included in all
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
copies or substantial portions of the Software.
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
==========================================================================
|
||||||
SOFTWARE.
|
END OF B.1 BSD 3-Clause license
|
||||||
==========================================================================
|
==========================================================================
|
||||||
END OF B.1 MIT license
|
|
||||||
==========================================================================
|
==========================================================================
|
||||||
|
END OF B. LICENSE FILES AND OTHER INFORMATION
|
||||||
==========================================================================
|
==========================================================================
|
||||||
B.2 BSD 3-Clause license
|
|
||||||
==========================================================================
|
===========================================================
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
==========================================================================
|
|
||||||
END OF B.2 BSD 3-Clause license
|
|
||||||
==========================================================================
|
|
||||||
|
|
||||||
==========================================================================
|
|
||||||
END OF B. LICENSE FILES AND OTHER INFORMATION
|
|
||||||
==========================================================================
|
|
||||||
|
|
||||||
===========================================================
|
|
||||||
END OF NOTICES AND INFORMATION
|
END OF NOTICES AND INFORMATION
|
||||||
15
README.md
15
README.md
@@ -2,21 +2,13 @@
|
|||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Run [IBM® MQ](http://www-03.ibm.com/software/products/en/ibm-mq) in a container. The supplied [Helm](https://helm.sh/) chart can be used to run the container on a [Kubernetes](https://kubernetes.io) cluster, such as [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/) or the [IBM Bluemix Container Service](https://www.ibm.com/cloud-computing/bluemix/containers).
|
Run [IBM® MQ](http://www-03.ibm.com/software/products/en/ibm-mq) in a container. The supplied [Helm](https://helm.sh/) charts can be used to run the container on a [Kubernetes](https://kubernetes.io) cluster, such as [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/) or the [IBM Cloud Container Service](https://www.ibm.com/cloud/container-service).
|
||||||
|
|
||||||
# Current status
|
# Current status
|
||||||
MQ Advanced for Developers image - [](https://travis-ci.org/ibm-messaging/mq-container)
|
MQ Advanced for Developers image - [](https://travis-ci.org/ibm-messaging/mq-container)
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
After extracting the code from this repository, you can build an image with the latest version of MQ using the following command:
|
After extracting the code from this repository, you can build an image by following the instructions [here](docs/building.md)
|
||||||
|
|
||||||
## Building MQ Advanced
|
|
||||||
You can build an image for MQ Advanced, follow these steps:
|
|
||||||
|
|
||||||
1. Clone this repository into the correct location in your [`GOPATH`](https://github.com/golang/go/wiki/GOPATH)
|
|
||||||
2. Create a directory called `downloads` under the cloned directory tree
|
|
||||||
3. Download the MQ Advanced for Ubuntu (debs) installer package file `CNJR7ML.tar.gz` into the `downloads` directory
|
|
||||||
4. Run `make build-advancedserver`
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
In order to use the image, it is necessary to accept the terms of the IBM MQ license. This is achieved by specifying the environment variable `LICENSE` equal to `accept` when running the image. You can also view the license terms by setting this variable to `view`. Failure to set the variable will result in the termination of the container with a usage statement. You can view the license in a different language by also setting the `LANG` environment variable.
|
In order to use the image, it is necessary to accept the terms of the IBM MQ license. This is achieved by specifying the environment variable `LICENSE` equal to `accept` when running the image. You can also view the license terms by setting this variable to `view`. Failure to set the variable will result in the termination of the container with a usage statement. You can view the license in a different language by also setting the `LANG` environment variable.
|
||||||
@@ -27,6 +19,7 @@ In order to use the image, it is necessary to accept the terms of the IBM MQ lic
|
|||||||
* **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
|
* **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
|
||||||
* **LANG** - Set this to the language you would like the license to be printed in.
|
* **LANG** - Set this to the language you would like the license to be printed in.
|
||||||
* **MQ_QMGR_NAME** - Set this to the name you want your Queue Manager to be created with.
|
* **MQ_QMGR_NAME** - Set this to the name you want your Queue Manager to be created with.
|
||||||
|
* **LOG_FORMAT** - Set this to change the format of the logs which are printed on the container's stdout. Set to "json" to use JSON format (JSON object per line); set to "basic" to use a simple human-readable format. Defaults to "basic".
|
||||||
|
|
||||||
|
|
||||||
# Issues and contributions
|
# Issues and contributions
|
||||||
@@ -47,4 +40,4 @@ Note: The IBM MQ Advanced for Developers license does not permit further distrib
|
|||||||
|
|
||||||
# Copyright
|
# Copyright
|
||||||
|
|
||||||
© Copyright IBM Corporation 2015, 2017
|
© Copyright IBM Corporation 2015, 2018
|
||||||
|
|||||||
@@ -15,6 +15,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
description: IBM MQ queue manager
|
description: IBM MQ queue manager
|
||||||
name: ibm-mqadvanced-server-dev
|
name: ibm-mqadvanced-server-dev
|
||||||
version: 1.0.2
|
version: 1.1.0
|
||||||
icon: https://developer.ibm.com/messaging/wp-content/uploads/sites/18/2017/07/IBM-MQ-Square-200.png
|
icon: https://developer.ibm.com/messaging/wp-content/uploads/sites/18/2017/12/ibm_mq_200.png
|
||||||
tillerVersion: ">=2.4.0"
|
tillerVersion: ">=2.4.0"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||

|
|
||||||
|
|
||||||
# IBM MQ
|
# IBM MQ
|
||||||
|
|
||||||
IBM® MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments.
|
IBM® MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments.
|
||||||
@@ -12,7 +10,7 @@ This chart deploys a single IBM MQ Advanced for Developers server (queue manager
|
|||||||
|
|
||||||
- Kubernetes 1.6 or greater, with beta APIs enabled
|
- Kubernetes 1.6 or greater, with beta APIs enabled
|
||||||
- If persistence is enabled (see [configuration](#configuration)), then you either need to create a PersistentVolume, or specify a Storage Class if classes are defined in your cluster.
|
- If persistence is enabled (see [configuration](#configuration)), then you either need to create a PersistentVolume, or specify a Storage Class if classes are defined in your cluster.
|
||||||
|
¸
|
||||||
## Installing the Chart
|
## Installing the Chart
|
||||||
|
|
||||||
To install the chart with the release name `foo`:
|
To install the chart with the release name `foo`:
|
||||||
@@ -63,7 +61,7 @@ The following table lists the configurable parameters of the `ibm-mqadvanced-ser
|
|||||||
| `queueManager.name` | MQ Queue Manager name | Helm release name |
|
| `queueManager.name` | MQ Queue Manager name | Helm release name |
|
||||||
| `queueManager.dev.adminPassword` | Developer defaults - administrator password | Random generated string. See the notes that appear when you install for how to retrieve this. |
|
| `queueManager.dev.adminPassword` | Developer defaults - administrator password | Random generated string. See the notes that appear when you install for how to retrieve this. |
|
||||||
| `queueManager.dev.appPassword` | Developer defaults - app password | `nil` (no password required to connect an MQ client) |
|
| `queueManager.dev.appPassword` | Developer defaults - app password | `nil` (no password required to connect an MQ client) |
|
||||||
| `nameOverride` | Set to partially override the resource names used in this chart | `nil` |
|
| `nameOverride` | Set to partially override the resource names used in this chart | `ibm-mq` |
|
||||||
|
|
||||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`.
|
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
{{- if .Capabilities.APIVersions.Has "apps/v1beta2" }}
|
||||||
|
apiVersion: apps/v1beta2
|
||||||
|
{{- else }}
|
||||||
apiVersion: apps/v1beta1
|
apiVersion: apps/v1beta1
|
||||||
|
{{- end }}
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ template "fullname" . }}
|
name: {{ template "fullname" . }}
|
||||||
@@ -22,9 +26,14 @@ metadata:
|
|||||||
release: "{{ .Release.Name }}"
|
release: "{{ .Release.Name }}"
|
||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
spec:
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ template "fullname" . }}
|
||||||
serviceName: {{ .Values.service.name }}
|
serviceName: {{ .Values.service.name }}
|
||||||
replicas: 1
|
replicas: 1
|
||||||
{{- if and (ge (.Capabilities.KubeVersion.Major | int) 1) (ge (.Capabilities.KubeVersion.Minor | int) 7) }}
|
{{- if and (.Capabilities.KubeVersion.Major | hasPrefix "1") (.Capabilities.KubeVersion.Minor | hasPrefix "7") }}
|
||||||
|
# Set updateStrategy to "RollingUpdate", if we're on Kubernetes 1.7.
|
||||||
|
# It's already the default for apps/v1beta2 (Kubernetes 1.8 onwards)
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -37,6 +46,19 @@ spec:
|
|||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
QM_IDENTIFIER: "{{ .Release.Name }}"
|
QM_IDENTIFIER: "{{ .Release.Name }}"
|
||||||
spec:
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: beta.kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- amd64
|
||||||
|
- key: beta.kubernetes.io/os
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- linux
|
||||||
{{- if .Values.image.pullSecret }}
|
{{- if .Values.image.pullSecret }}
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: {{ .Values.image.pullSecret }}
|
- name: {{ .Values.image.pullSecret }}
|
||||||
|
|||||||
176
charts/ibm-mqadvanced-server-dev/values-metadata.yaml
Normal file
176
charts/ibm-mqadvanced-server-dev/values-metadata.yaml
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
license:
|
||||||
|
__metadata:
|
||||||
|
label: "License"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "Accepted"
|
||||||
|
value: "accept"
|
||||||
|
- label: "Not accepted"
|
||||||
|
value: "not accepted"
|
||||||
|
|
||||||
|
image:
|
||||||
|
__metadata:
|
||||||
|
label: "Image"
|
||||||
|
repository:
|
||||||
|
__metadata:
|
||||||
|
label: "Image repository"
|
||||||
|
description: "Docker image location"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
tag:
|
||||||
|
__metadata:
|
||||||
|
label: "Image tag"
|
||||||
|
description: "Docker image tag"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
pullPolicy:
|
||||||
|
__metadata:
|
||||||
|
name: "pullPolicy"
|
||||||
|
label: "Image pull policy"
|
||||||
|
description: "Always, Never, or IfNotPresent. Defaults to Always"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "Always"
|
||||||
|
value: "Always"
|
||||||
|
- label: "Never"
|
||||||
|
value: "Never"
|
||||||
|
- label: "IfNotPresent"
|
||||||
|
value: "IfNotPresent"
|
||||||
|
pullSecret:
|
||||||
|
__metadata:
|
||||||
|
label: "Image pull secret"
|
||||||
|
description: "Secret to use when pulling the image. Set this when using an image from a private registry"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
## global persistence parameters
|
||||||
|
persistence:
|
||||||
|
__metadata:
|
||||||
|
label: "Persistence"
|
||||||
|
enabled:
|
||||||
|
__metadata:
|
||||||
|
label: "Enable persistence"
|
||||||
|
description: "Whether or not to store MQ messages and configuration on a Persistent Volume"
|
||||||
|
type: "boolean"
|
||||||
|
required: true
|
||||||
|
useDynamicProvisioning:
|
||||||
|
__metadata:
|
||||||
|
label: "Use dynamic provisioning"
|
||||||
|
description: "Whether or not to use Storage Classes to provision a Persisent Volume automatically"
|
||||||
|
type: "boolean"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
dataPVC:
|
||||||
|
__metadata:
|
||||||
|
label: "Data PVC"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Name"
|
||||||
|
description: "Name of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
storageClassName:
|
||||||
|
__metadata:
|
||||||
|
label: "Storage Class name"
|
||||||
|
description: "Storage class of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
size:
|
||||||
|
__metadata:
|
||||||
|
label: "Size"
|
||||||
|
description: "Size of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
service:
|
||||||
|
__metadata:
|
||||||
|
label: "Service"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Service name"
|
||||||
|
description: "Service name"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
type:
|
||||||
|
__metadata:
|
||||||
|
label: "Service type"
|
||||||
|
description: "Type of service"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "ClusterIP"
|
||||||
|
value: "ClusterIP"
|
||||||
|
- label: "NodePort"
|
||||||
|
value: "NodePort"
|
||||||
|
- label: "LoadBalancer"
|
||||||
|
value: "LoadBalancer"
|
||||||
|
- label: "ExternalName"
|
||||||
|
value: "ExternalName"
|
||||||
|
|
||||||
|
resources:
|
||||||
|
__metadata:
|
||||||
|
label: "Resources"
|
||||||
|
requests:
|
||||||
|
cpu:
|
||||||
|
__metadata:
|
||||||
|
label: "CPU request"
|
||||||
|
description: "The requested CPU"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
memory:
|
||||||
|
__metadata:
|
||||||
|
label: "Memory request"
|
||||||
|
description: "The requested memory"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
limits:
|
||||||
|
cpu:
|
||||||
|
__metadata:
|
||||||
|
label: "CPU limit"
|
||||||
|
description: "The CPU limit"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
memory:
|
||||||
|
__metadata:
|
||||||
|
label: "Memory limit"
|
||||||
|
description: "The memory limit"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
queueManager:
|
||||||
|
__metadata:
|
||||||
|
label: "Queue manager"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Queue manager name"
|
||||||
|
description: "MQ queue manager name, which defaults to the Helm release name"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
dev:
|
||||||
|
__metadata:
|
||||||
|
label: "Default Developer Configuration"
|
||||||
|
description: "Configure some queue manager basics, suitable for developer use"
|
||||||
|
adminPassword:
|
||||||
|
__metadata:
|
||||||
|
label: "Admin password"
|
||||||
|
description: "Password for 'admin' user"
|
||||||
|
type: "password"
|
||||||
|
required: false
|
||||||
|
appPassword:
|
||||||
|
__metadata:
|
||||||
|
label: "App password"
|
||||||
|
description: "Password for 'app' user to use for messaging"
|
||||||
|
type: "password"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
nameOverride:
|
||||||
|
__metadata:
|
||||||
|
label: "Name override"
|
||||||
|
description: "This can be set to partially override the name of the resources created by this chart"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
@@ -65,4 +65,4 @@ queueManager:
|
|||||||
appPassword:
|
appPassword:
|
||||||
|
|
||||||
# nameOverride can be set to partially override the name of the resources created by this chart
|
# nameOverride can be set to partially override the name of the resources created by this chart
|
||||||
nameOverride:
|
nameOverride: ibm-mq
|
||||||
|
|||||||
@@ -15,6 +15,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
description: IBM MQ queue manager
|
description: IBM MQ queue manager
|
||||||
name: ibm-mqadvanced-server-prod
|
name: ibm-mqadvanced-server-prod
|
||||||
version: 1.0.2
|
version: 1.1.0
|
||||||
icon: https://developer.ibm.com/messaging/wp-content/uploads/sites/18/2017/07/IBM-MQ-Square-200.png
|
icon: https://developer.ibm.com/messaging/wp-content/uploads/sites/18/2017/12/ibm_mq_200.png
|
||||||
tillerVersion: ">=2.4.0"
|
tillerVersion: ">=2.4.0"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||

|
|
||||||
|
|
||||||
# IBM MQ
|
# IBM MQ
|
||||||
|
|
||||||
IBM® MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments.
|
IBM® MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments.
|
||||||
@@ -45,8 +43,8 @@ The following table lists the configurable parameters of the `ibm-mqadvanced-ser
|
|||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
| ------------------------------- | --------------------------------------------------------------- | ------------------------------------------ |
|
| ------------------------------- | --------------------------------------------------------------- | ------------------------------------------ |
|
||||||
| `license` | Set to `accept` to accept the terms of the IBM license | `"not accepted"` |
|
| `license` | Set to `accept` to accept the terms of the IBM license | `"not accepted"` |
|
||||||
| `image.repository` | Image full name including repository | `nil` |
|
| `image.repository` | Image full name including repository | `MQ image in your registry` |
|
||||||
| `image.tag` | Image tag | `nil` |
|
| `image.tag` | Image tag | `Tag of MQ image in your registry` |
|
||||||
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||||
| `image.pullSecret` | Image pull secret, if you are using a private Docker registry | `nil` |
|
| `image.pullSecret` | Image pull secret, if you are using a private Docker registry | `nil` |
|
||||||
| `persistence.enabled` | Use persistent volumes for all defined volumes | `true` |
|
| `persistence.enabled` | Use persistent volumes for all defined volumes | `true` |
|
||||||
@@ -61,7 +59,7 @@ The following table lists the configurable parameters of the `ibm-mqadvanced-ser
|
|||||||
| `resources.requests.cpu` | Kubernetes CPU request for the Queue Manager container | `1` |
|
| `resources.requests.cpu` | Kubernetes CPU request for the Queue Manager container | `1` |
|
||||||
| `resources.requests.memory` | Kubernetes memory request for the Queue Manager container | `1Gi` |
|
| `resources.requests.memory` | Kubernetes memory request for the Queue Manager container | `1Gi` |
|
||||||
| `queueManager.name` | MQ Queue Manager name | Helm release name |
|
| `queueManager.name` | MQ Queue Manager name | Helm release name |
|
||||||
| `nameOverride` | Set to partially override the resource names used in this chart | `nil` |
|
| `nameOverride` | Set to partially override the resource names used in this chart | `ibm-mq` |
|
||||||
| `livenessProbe.initialDelaySeconds` | The initial delay before starting the liveness probe. Useful for slower systems that take longer to start the Queue Manager. | 60 |
|
| `livenessProbe.initialDelaySeconds` | The initial delay before starting the liveness probe. Useful for slower systems that take longer to start the Queue Manager. | 60 |
|
||||||
| `livenessProbe.periodSeconds` | How often to run the probe | 10 |
|
| `livenessProbe.periodSeconds` | How often to run the probe | 10 |
|
||||||
| `livenessProbe.timeoutSeconds` | Number of seconds after which the probe times out | 5 |
|
| `livenessProbe.timeoutSeconds` | Number of seconds after which the probe times out | 5 |
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
{{- if .Capabilities.APIVersions.Has "apps/v1beta2" }}
|
||||||
|
apiVersion: apps/v1beta2
|
||||||
|
{{- else }}
|
||||||
apiVersion: apps/v1beta1
|
apiVersion: apps/v1beta1
|
||||||
|
{{- end }}
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ template "fullname" . }}
|
name: {{ template "fullname" . }}
|
||||||
@@ -22,9 +26,14 @@ metadata:
|
|||||||
release: "{{ .Release.Name }}"
|
release: "{{ .Release.Name }}"
|
||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
spec:
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ template "fullname" . }}
|
||||||
serviceName: {{ .Values.service.name }}
|
serviceName: {{ .Values.service.name }}
|
||||||
replicas: 1
|
replicas: 1
|
||||||
{{- if and (ge (.Capabilities.KubeVersion.Major | int) 1) (ge (.Capabilities.KubeVersion.Minor | int) 7) }}
|
{{- if and (.Capabilities.KubeVersion.Major | hasPrefix "1") (.Capabilities.KubeVersion.Minor | hasPrefix "7") }}
|
||||||
|
# Set updateStrategy to "RollingUpdate", if we're on Kubernetes 1.7.
|
||||||
|
# It's already the default for apps/v1beta2 (Kubernetes 1.8 onwards)
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -37,6 +46,19 @@ spec:
|
|||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
QM_IDENTIFIER: "{{ .Release.Name }}"
|
QM_IDENTIFIER: "{{ .Release.Name }}"
|
||||||
spec:
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: beta.kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- amd64
|
||||||
|
- key: beta.kubernetes.io/os
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- linux
|
||||||
{{- if .Values.image.pullSecret }}
|
{{- if .Values.image.pullSecret }}
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: {{ .Values.image.pullSecret }}
|
- name: {{ .Values.image.pullSecret }}
|
||||||
|
|||||||
219
charts/ibm-mqadvanced-server-prod/values-metadata.yaml
Normal file
219
charts/ibm-mqadvanced-server-prod/values-metadata.yaml
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
license:
|
||||||
|
__metadata:
|
||||||
|
label: "License"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "Accepted"
|
||||||
|
value: "accept"
|
||||||
|
- label: "Not accepted"
|
||||||
|
value: "not accepted"
|
||||||
|
|
||||||
|
image:
|
||||||
|
__metadata:
|
||||||
|
label: "Image"
|
||||||
|
repository:
|
||||||
|
__metadata:
|
||||||
|
label: "Image repository"
|
||||||
|
description: "Docker image location"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
tag:
|
||||||
|
__metadata:
|
||||||
|
label: "Image tag"
|
||||||
|
description: "Docker image tag"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
pullPolicy:
|
||||||
|
__metadata:
|
||||||
|
name: "pullPolicy"
|
||||||
|
label: "Image pull policy"
|
||||||
|
description: "Always, Never, or IfNotPresent. Defaults to Always"
|
||||||
|
type: "string"
|
||||||
|
immutable: false
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "Always"
|
||||||
|
value: "Always"
|
||||||
|
- label: "Never"
|
||||||
|
value: "Never"
|
||||||
|
- label: "IfNotPresent"
|
||||||
|
value: "IfNotPresent"
|
||||||
|
pullSecret:
|
||||||
|
__metadata:
|
||||||
|
label: "Image pull secret"
|
||||||
|
description: "Secret to use when pulling the image. Set this when using an image from a private registry"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
## global persistence parameters
|
||||||
|
persistence:
|
||||||
|
__metadata:
|
||||||
|
label: "Persistence"
|
||||||
|
enabled:
|
||||||
|
__metadata:
|
||||||
|
label: "Enable persistence"
|
||||||
|
description: "Whether or not to store MQ messages and configuration on a Persistent Volume"
|
||||||
|
type: "boolean"
|
||||||
|
required: true
|
||||||
|
useDynamicProvisioning:
|
||||||
|
__metadata:
|
||||||
|
label: "Use dynamic provisioning"
|
||||||
|
description: "Whether or not to use Storage Classes to provision a Persisent Volume automatically"
|
||||||
|
type: "boolean"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
dataPVC:
|
||||||
|
__metadata:
|
||||||
|
label: "Data PVC"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Name"
|
||||||
|
description: "Name of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
storageClassName:
|
||||||
|
__metadata:
|
||||||
|
label: "Storage Class name"
|
||||||
|
description: "Storage class of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
size:
|
||||||
|
__metadata:
|
||||||
|
label: "Size"
|
||||||
|
description: "Size of Persistent Volume Claim, used for MQ objects and messages"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
service:
|
||||||
|
__metadata:
|
||||||
|
label: "Service"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Service name"
|
||||||
|
description: "Service name"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
type:
|
||||||
|
__metadata:
|
||||||
|
label: "Service type"
|
||||||
|
description: "Type of service"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- label: "ClusterIP"
|
||||||
|
value: "ClusterIP"
|
||||||
|
- label: "NodePort"
|
||||||
|
value: "NodePort"
|
||||||
|
- label: "LoadBalancer"
|
||||||
|
value: "LoadBalancer"
|
||||||
|
- label: "ExternalName"
|
||||||
|
value: "ExternalName"
|
||||||
|
|
||||||
|
resources:
|
||||||
|
__metadata:
|
||||||
|
label: "Resources"
|
||||||
|
requests:
|
||||||
|
cpu:
|
||||||
|
__metadata:
|
||||||
|
label: "CPU request"
|
||||||
|
description: "The requested CPU"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
memory:
|
||||||
|
__metadata:
|
||||||
|
label: "Memory request"
|
||||||
|
description: "The requested memory"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
limits:
|
||||||
|
cpu:
|
||||||
|
__metadata:
|
||||||
|
label: "CPU limit"
|
||||||
|
description: "The CPU limit"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
memory:
|
||||||
|
__metadata:
|
||||||
|
label: "Memory limit"
|
||||||
|
description: "The memory limit"
|
||||||
|
type: "string"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
queueManager:
|
||||||
|
__metadata:
|
||||||
|
label: "Queue manager"
|
||||||
|
name:
|
||||||
|
__metadata:
|
||||||
|
label: "Queue manager name"
|
||||||
|
description: "MQ queue manager name, which defaults to the Helm release name"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
nameOverride:
|
||||||
|
__metadata:
|
||||||
|
label: "Name override"
|
||||||
|
description: "This can be set to partially override the name of the resources created by this chart"
|
||||||
|
type: "string"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
# livenessProbe section specifies setting for the MQ liveness probe, which checks for a running Queue Manager
|
||||||
|
livenessProbe:
|
||||||
|
__metadata:
|
||||||
|
label: "Liveness probe"
|
||||||
|
# initialDelaySeconds should be raised if your system cannot start the Queue Manager in 60 seconds
|
||||||
|
initialDelaySeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Initial delay (seconds)"
|
||||||
|
description: "How long to wait before starting the probe. Raise this delay if your system cannot start the Queue Manager in the default time period"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
periodSeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Period (seconds)"
|
||||||
|
description: "How often to perform the probe"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
timeoutSeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Timeout (seconds)"
|
||||||
|
description: "How long before a probe times out"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
failureThreshold:
|
||||||
|
__metadata:
|
||||||
|
label: "Failure threshold"
|
||||||
|
description: "Number of times the probe can fail before taking action"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
# readinessProbe section specifies setting for the MQ readiness probe, which checks when the MQ listener is running
|
||||||
|
readinessProbe:
|
||||||
|
__metadata:
|
||||||
|
label: "Readiness probe"
|
||||||
|
initialDelaySeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Initial delay (seconds)"
|
||||||
|
description: "How long to wait before starting the probe"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
periodSeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Period (seconds)"
|
||||||
|
description: "How often to perform the probe"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
timeoutSeconds:
|
||||||
|
__metadata:
|
||||||
|
label: "Timeout (seconds)"
|
||||||
|
description: "How long before a probe times out"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
|
failureThreshold:
|
||||||
|
__metadata:
|
||||||
|
label: "Failure threshold"
|
||||||
|
description: "Number of times the probe can fail before taking action"
|
||||||
|
type: "number"
|
||||||
|
required: false
|
||||||
@@ -59,7 +59,7 @@ queueManager:
|
|||||||
name:
|
name:
|
||||||
|
|
||||||
# nameOverride can be set to partially override the name of the resources created by this chart
|
# nameOverride can be set to partially override the name of the resources created by this chart
|
||||||
nameOverride:
|
nameOverride: ibm-mq
|
||||||
|
|
||||||
# livenessProbe section specifies setting for the MQ liveness probe, which checks for a running Queue Manager
|
# livenessProbe section specifies setting for the MQ liveness probe, which checks for a running Queue Manager
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
"github.com/ibm-messaging/mq-container/internal/name"
|
||||||
)
|
)
|
||||||
|
|
||||||
func queueManagerHealthy() (bool, error) {
|
func queueManagerHealthy() (bool, error) {
|
||||||
@@ -35,8 +36,10 @@ func queueManagerHealthy() (bool, error) {
|
|||||||
// Run the command and wait for completion
|
// Run the command and wait for completion
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
fmt.Println(out)
|
||||||
if !strings.Contains(string(out), "(RUNNING)") {
|
if !strings.Contains(string(out), "(RUNNING)") {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -18,13 +18,23 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Check if runmqserver has indicated that it's finished configuration
|
||||||
|
r, err := ready.Check()
|
||||||
|
if !r || err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// Check if the queue manager has a running listener
|
||||||
conn, err := net.Dial("tcp", "127.0.0.1:1414")
|
conn, err := net.Dial("tcp", "127.0.0.1:1414")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
|||||||
145
cmd/runmqdevserver/main.go
Normal file
145
cmd/runmqdevserver/main.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/command"
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/name"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log *logger.Logger
|
||||||
|
|
||||||
|
func setPassword(user string, password string) error {
|
||||||
|
cmd := exec.Command("chpasswd")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stdin, "%s:%s", user, password)
|
||||||
|
stdin.Close()
|
||||||
|
_, _, err = command.RunCmd(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Set password for \"%v\" user", user)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogFormat() string {
|
||||||
|
return os.Getenv("LOG_FORMAT")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDebug() bool {
|
||||||
|
debug := os.Getenv("DEBUG")
|
||||||
|
if debug == "true" || debug == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureLogger() error {
|
||||||
|
var err error
|
||||||
|
f := getLogFormat()
|
||||||
|
d := getDebug()
|
||||||
|
n, err := name.GetQueueManagerName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch f {
|
||||||
|
case "json":
|
||||||
|
log, err = logger.NewLogger(os.Stderr, d, true, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "basic":
|
||||||
|
log, err = logger.NewLogger(os.Stderr, d, false, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log, err = logger.NewLogger(os.Stdout, d, false, n)
|
||||||
|
return fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logTerminationf(format string, args ...interface{}) {
|
||||||
|
logTermination(fmt.Sprintf(format, args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Duplicated code
|
||||||
|
func logTermination(args ...interface{}) {
|
||||||
|
msg := fmt.Sprint(args)
|
||||||
|
// Write the message to the termination log. This is the default place
|
||||||
|
// that Kubernetes will look for termination information.
|
||||||
|
log.Debugf("Writing termination message: %v", msg)
|
||||||
|
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
}
|
||||||
|
log.Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doMain() error {
|
||||||
|
err := configureLogger()
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
adminPassword, set := os.LookupEnv("MQ_ADMIN_PASSWORD")
|
||||||
|
if set {
|
||||||
|
err = setPassword("admin", adminPassword)
|
||||||
|
if err != nil {
|
||||||
|
logTerminationf("Error setting admin password: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appPassword, set := os.LookupEnv("MQ_APP_PASSWORD")
|
||||||
|
if set {
|
||||||
|
err = setPassword("app", appPassword)
|
||||||
|
if err != nil {
|
||||||
|
logTerminationf("Error setting app password: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateMQSC(set)
|
||||||
|
if err != nil {
|
||||||
|
logTerminationf("Error updating MQSC: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var osExit = os.Exit
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := doMain()
|
||||||
|
if err != nil {
|
||||||
|
osExit(1)
|
||||||
|
} else {
|
||||||
|
// Replace this process with runmqserver
|
||||||
|
syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver"}, os.Environ())
|
||||||
|
}
|
||||||
|
}
|
||||||
57
cmd/runmqdevserver/mqsc.go
Normal file
57
cmd/runmqdevserver/mqsc.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateMQSC(appPasswordRequired bool) error {
|
||||||
|
var checkClient string
|
||||||
|
if appPasswordRequired {
|
||||||
|
checkClient = "REQUIRED"
|
||||||
|
} else {
|
||||||
|
checkClient = "ASQMGR"
|
||||||
|
}
|
||||||
|
const mqsc string = "/etc/mqm/dev.mqsc"
|
||||||
|
if os.Getenv("MQ_DEV") == "true" {
|
||||||
|
const mqscTemplate string = mqsc + ".tpl"
|
||||||
|
// Re-configure channel if app password not set
|
||||||
|
t, err := template.ParseFiles(mqscTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(mqsc, os.O_CREATE|os.O_WRONLY, 0660)
|
||||||
|
defer f.Close()
|
||||||
|
err = t.Execute(f, map[string]string{"ChckClnt": checkClient})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: Lookup value for MQM user here?
|
||||||
|
err = os.Chown(mqsc, 999, 999)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// os.Remove(mqscTemplate)
|
||||||
|
} else {
|
||||||
|
os.Remove(mqsc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -16,15 +16,29 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mqmUID uint32 = 999
|
func lookupMQM() (int, int, error) {
|
||||||
const mqmGID uint32 = 999
|
mqm, err := user.Lookup("mqm")
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
mqmUID, err := strconv.Atoi(mqm.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
mqmGID, err := strconv.Atoi(mqm.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return mqmUID, mqmGID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func createVolume(path string) error {
|
func createVolume(path string) error {
|
||||||
dataPath := filepath.Join(path, "data")
|
dataPath := filepath.Join(path, "data")
|
||||||
@@ -46,8 +60,13 @@ func createVolume(path string) error {
|
|||||||
sys := fi.Sys()
|
sys := fi.Sys()
|
||||||
if sys != nil && runtime.GOOS == "linux" {
|
if sys != nil && runtime.GOOS == "linux" {
|
||||||
stat := sys.(*syscall.Stat_t)
|
stat := sys.(*syscall.Stat_t)
|
||||||
if stat.Uid != mqmUID || stat.Gid != mqmGID {
|
mqmUID, mqmGID, err := lookupMQM()
|
||||||
err = os.Chown(dataPath, int(mqmUID), int(mqmGID))
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("mqm user is %v (%v)", mqmUID, mqmGID)
|
||||||
|
if int(stat.Uid) != mqmUID || int(stat.Gid) != mqmGID {
|
||||||
|
err = os.Chown(dataPath, mqmUID, mqmGID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error: Unable to change ownership of %v", dataPath)
|
log.Printf("Error: Unable to change ownership of %v", dataPath)
|
||||||
return err
|
return err
|
||||||
|
|||||||
92
cmd/runmqserver/license.go
Normal file
92
cmd/runmqserver/license.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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"
|
||||||
|
"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"
|
||||||
|
// 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"
|
||||||
|
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"
|
||||||
|
// 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"
|
||||||
|
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")
|
||||||
|
}
|
||||||
282
cmd/runmqserver/license_test.go
Normal file
282
cmd/runmqserver/license_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
cmd/runmqserver/logging.go
Normal file
121
cmd/runmqserver/logging.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/mqini"
|
||||||
|
)
|
||||||
|
|
||||||
|
// var debug = false
|
||||||
|
var log *logger.Logger
|
||||||
|
|
||||||
|
func logDebug(args ...interface{}) {
|
||||||
|
log.Debug(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logDebugf(format string, args ...interface{}) {
|
||||||
|
log.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logTerminationf(format string, args ...interface{}) {
|
||||||
|
logTermination(fmt.Sprintf(format, args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func logTermination(args ...interface{}) {
|
||||||
|
msg := fmt.Sprint(args)
|
||||||
|
// Write the message to the termination log. This is the default place
|
||||||
|
// that Kubernetes will look for termination information.
|
||||||
|
log.Debugf("Writing termination message: %v", msg)
|
||||||
|
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
}
|
||||||
|
log.Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogFormat() string {
|
||||||
|
return os.Getenv("LOG_FORMAT")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSimple(datetime string, message string) string {
|
||||||
|
return fmt.Sprintf("%v %v\n", datetime, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirrorSystemErrorLogs starts a goroutine to mirror the contents of the MQ system error logs
|
||||||
|
func mirrorSystemErrorLogs(ctx context.Context, wg *sync.WaitGroup, mf mirrorFunc) (chan error, error) {
|
||||||
|
// Always use the JSON log as the source
|
||||||
|
return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirrorQueueManagerErrorLogs starts a goroutine to mirror the contents of the MQ queue manager error logs
|
||||||
|
func mirrorQueueManagerErrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||||
|
// Always use the JSON log as the source
|
||||||
|
qm, err := mqini.GetQueueManager(name)
|
||||||
|
if err != nil {
|
||||||
|
logDebug(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
|
||||||
|
return mirrorLog(ctx, wg, f, fromStart, mf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDebug() bool {
|
||||||
|
debug := os.Getenv("DEBUG")
|
||||||
|
if debug == "true" || debug == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureLogger(name string) (mirrorFunc, error) {
|
||||||
|
var err error
|
||||||
|
f := getLogFormat()
|
||||||
|
d := getDebug()
|
||||||
|
switch f {
|
||||||
|
case "json":
|
||||||
|
log, err = logger.NewLogger(os.Stderr, d, true, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return log.LogDirect, nil
|
||||||
|
case "basic":
|
||||||
|
log, err = logger.NewLogger(os.Stderr, d, false, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(msg string) {
|
||||||
|
// Parse the JSON message, and print a simplified version
|
||||||
|
var obj map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(msg), &obj)
|
||||||
|
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string)))
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
log, err = logger.NewLogger(os.Stdout, d, false, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -18,279 +18,119 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"io/ioutil"
|
"errors"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"sync"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"github.com/ibm-messaging/mq-container/internal/name"
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||||
)
|
)
|
||||||
|
|
||||||
// resolveLicenseFile returns the file name of the MQ license file, taking into
|
func doMain() error {
|
||||||
// account the language set by the LANG environment variable
|
name, nameErr := name.GetQueueManagerName()
|
||||||
func resolveLicenseFile() string {
|
mf, err := configureLogger(name)
|
||||||
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 {
|
if err != nil {
|
||||||
var rc int
|
logTermination(err)
|
||||||
// Only works on Linux
|
return err
|
||||||
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
|
if nameErr != nil {
|
||||||
}
|
logTermination(err)
|
||||||
|
return err
|
||||||
// createDirStructure creates the default MQ directory structure under /var/mqm
|
}
|
||||||
func createDirStructure() {
|
err = ready.Clear()
|
||||||
out, _, err := runCommand("/opt/mqm/bin/crtmqdir", "-f", "-s")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating directory structure: %v\n", string(out))
|
logTermination(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
log.Println("Created directory structure under /var/mqm")
|
accepted, err := checkLicense()
|
||||||
}
|
|
||||||
|
|
||||||
func createQueueManager(name string) {
|
|
||||||
log.Printf("Creating queue manager %v", name)
|
|
||||||
out, rc, err := runCommand("crtmqm", "-q", "-p", "1414", name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 8=Queue manager exists, which is fine
|
logTerminationf("Error checking license acceptance: %v", err)
|
||||||
if rc != 8 {
|
return err
|
||||||
log.Printf("crtmqm returned %v", rc)
|
|
||||||
log.Fatalln(string(out))
|
|
||||||
} else {
|
|
||||||
log.Printf("Detected existing queue manager %v", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
if !accepted {
|
||||||
|
err = errors.New("License not accepted")
|
||||||
func updateCommandLevel() {
|
logTermination(err)
|
||||||
level, ok := os.LookupEnv("MQ_CMDLEVEL")
|
return err
|
||||||
if ok && level != "" {
|
|
||||||
out, rc, err := runCommand("strmqm", "-e", "CMDLEVEL="+level)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error %v setting CMDLEVEL: %v", rc, string(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startQueueManager() {
|
|
||||||
log.Println("Starting queue manager")
|
|
||||||
out, rc, err := runCommand("strmqm")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error %v starting queue manager: %v", rc, string(out))
|
|
||||||
}
|
|
||||||
log.Println("Started queue manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureQueueManager() {
|
|
||||||
const configDir string = "/etc/mqm"
|
|
||||||
files, err := ioutil.ReadDir(configDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if strings.HasSuffix(file.Name(), ".mqsc") {
|
|
||||||
abs := filepath.Join(configDir, file.Name())
|
|
||||||
mqsc, err := ioutil.ReadFile(abs)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("runmqsc")
|
|
||||||
stdin, err := cmd.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
stdin.Write(mqsc)
|
|
||||||
stdin.Close()
|
|
||||||
// Run the command and wait for completion
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
// Print the runmqsc output, adding tab characters to make it more readable as part of the log
|
|
||||||
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, strings.Replace(string(out), "\n", "\n\t", -1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopQueueManager() {
|
|
||||||
log.Println("Stopping queue manager")
|
|
||||||
out, _, err := runCommand("endmqm", "-w")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error stopping queue manager: %v", string(out))
|
|
||||||
}
|
|
||||||
log.Println("Stopped queue manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
}
|
||||||
log.Printf("Using queue manager name: %v", name)
|
log.Printf("Using queue manager name: %v", name)
|
||||||
|
|
||||||
|
// Start signal handler
|
||||||
|
signalControl := signalHandler(name)
|
||||||
|
|
||||||
logConfig()
|
logConfig()
|
||||||
err = createVolume("/mnt/mqm")
|
err = createVolume("/mnt/mqm")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = createDirStructure()
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = postInit(name)
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newQM, err := createQueueManager(name)
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer func() {
|
||||||
|
log.Debug("Waiting for log mirroring to complete")
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
ctx, cancelMirror := context.WithCancel(context.Background())
|
||||||
|
defer func() {
|
||||||
|
log.Debug("Cancel log mirroring")
|
||||||
|
cancelMirror()
|
||||||
|
}()
|
||||||
|
// TODO: Use the error channel
|
||||||
|
_, err = mirrorSystemErrorLogs(ctx, &wg, mf)
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = mirrorQueueManagerErrorLogs(ctx, &wg, name, newQM, mf)
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = updateCommandLevel()
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = startQueueManager()
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
createDirStructure()
|
|
||||||
createQueueManager(name)
|
|
||||||
updateCommandLevel()
|
|
||||||
startQueueManager()
|
|
||||||
configureQueueManager()
|
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
|
||||||
|
// Write a file to indicate that chkmqready should now work as normal
|
||||||
|
ready.Set()
|
||||||
// Wait for terminate signal
|
// Wait for terminate signal
|
||||||
<-done
|
<-signalControl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var osExit = os.Exit
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := doMain()
|
||||||
|
if err != nil {
|
||||||
|
osExit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -17,18 +17,38 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var test *bool
|
var test *bool
|
||||||
|
|
||||||
|
const filename = "/var/coverage/exitCode"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
test = flag.Bool("test", false, "Set to true when running tests for coverage")
|
test = flag.Bool("test", false, "Set to true when running tests for coverage")
|
||||||
|
log, _ = logger.NewLogger(os.Stdout, true, false, "test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test started when the test binary is started. Only calls main.
|
// Test started when the test binary is started. Only calls main.
|
||||||
func TestSystem(t *testing.T) {
|
func TestSystem(t *testing.T) {
|
||||||
if *test {
|
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 %v", strconv.Itoa(rc), filename)
|
||||||
|
err := ioutil.WriteFile(filename, []byte(strconv.Itoa(rc)), 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
main()
|
main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
189
cmd/runmqserver/mirror.go
Normal file
189
cmd/runmqserver/mirror.go
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// waitForFile waits until the specified file exists
|
||||||
|
func waitForFile(ctx context.Context, path string) (os.FileInfo, error) {
|
||||||
|
var fi os.FileInfo
|
||||||
|
var err error
|
||||||
|
// Wait for file to exist
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// Check to see if cancellation has been requested
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
fi, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("mirror: unable to get info on file %v", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("File exists: %v, %v", path, fi.Size())
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mirrorFunc func(msg string)
|
||||||
|
|
||||||
|
// mirrorAvailableMessages prints lines from the file, until no more are available
|
||||||
|
func mirrorAvailableMessages(f *os.File, mf mirrorFunc) {
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
count := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
t := scanner.Text()
|
||||||
|
mf(t)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
log.Debugf("Mirrored %v log entries from %v", count, f.Name())
|
||||||
|
}
|
||||||
|
err := scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error reading file %v: %v", f.Name(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirrorLog tails the specified file, and logs each line to stdout.
|
||||||
|
// This is useful for usability, as the container console log can show
|
||||||
|
// messages from the MQ error logs.
|
||||||
|
func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||||
|
errorChannel := make(chan error, 1)
|
||||||
|
var offset int64 = -1
|
||||||
|
var f *os.File
|
||||||
|
var err error
|
||||||
|
var fi os.FileInfo
|
||||||
|
// Need to check if the file exists before returning, otherwise we have a
|
||||||
|
// race to see if the new file get created before we can test for it
|
||||||
|
fi, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// File doesn't exist, so ensure we start at the beginning
|
||||||
|
offset = 0
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the file exists, open it now, before we return. This makes sure
|
||||||
|
// the file is open before the queue manager is created or started.
|
||||||
|
// Otherwise, there would be the potential for a nearly-full file to
|
||||||
|
// rotate before the goroutine had a chance to open it.
|
||||||
|
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// File already exists, so start reading at the end
|
||||||
|
offset = fi.Size()
|
||||||
|
}
|
||||||
|
// Increment wait group counter, only if the goroutine gets started
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
// Notify the wait group when this goroutine ends
|
||||||
|
defer func() {
|
||||||
|
log.Debugf("Finished monitoring %v", path)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
if f == nil {
|
||||||
|
// File didn't exist, so need to wait for it
|
||||||
|
fi, err = waitForFile(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// The file now exists. If it didn't exist before we started, offset=0
|
||||||
|
// Always start at the beginning if we've been told to go from the start
|
||||||
|
if offset != 0 && !fromStart {
|
||||||
|
log.Debugf("Seeking offset %v in file %v", offset, path)
|
||||||
|
f.Seek(offset, 0)
|
||||||
|
}
|
||||||
|
closing := false
|
||||||
|
for {
|
||||||
|
// If there's already data there, mirror it now.
|
||||||
|
mirrorAvailableMessages(f, mf)
|
||||||
|
newFI, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !os.SameFile(fi, newFI) {
|
||||||
|
log.Debugf("Detected log rotation in file %v", path)
|
||||||
|
// WARNING: There is a possible race condition here. If *another*
|
||||||
|
// log rotation happens before we can open the new file, then we
|
||||||
|
// could skip all those messages. This could happen with a very small
|
||||||
|
// MQ error log size.
|
||||||
|
mirrorAvailableMessages(f, mf)
|
||||||
|
f.Close()
|
||||||
|
// Re-open file
|
||||||
|
log.Debugf("Re-opening error log file %v", path)
|
||||||
|
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi = newFI
|
||||||
|
// Don't seek this time, because we know it's a new file
|
||||||
|
mirrorAvailableMessages(f, mf)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Debugf("Context cancelled for mirroring %v", path)
|
||||||
|
if closing {
|
||||||
|
log.Debugf("Shutting down mirror for %v", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set a flag, to allow one more time through the loop
|
||||||
|
closing = true
|
||||||
|
default:
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return errorChannel, nil
|
||||||
|
}
|
||||||
191
cmd/runmqserver/mirror_test.go
Normal file
191
cmd/runmqserver/mirror_test.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMirrorLogWithoutRotation(t *testing.T) {
|
||||||
|
// Repeat the test multiple times, to help identify timing problems
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
t.Run(t.Name()+strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
// Use just the sub-test name in the file name
|
||||||
|
tmp, err := ioutil.TempFile("", strings.Split(t.Name(), "/")[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(tmp.Name())
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
count := 0
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) {
|
||||||
|
count++
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Logging 3 JSON messages")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"A\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||||
|
f.Close()
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
if count != 3 {
|
||||||
|
t.Fatalf("Expected 3 log entries; got %v", count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMirrorLogWithRotation(t *testing.T) {
|
||||||
|
// Repeat the test multiple times, to help identify timing problems
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
t.Run(t.Name()+strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
// Use just the sub-test name in the file name
|
||||||
|
tmp, err := ioutil.TempFile("", strings.Split(t.Name(), "/")[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(tmp.Name())
|
||||||
|
defer func() {
|
||||||
|
t.Log("Removing file")
|
||||||
|
os.Remove(tmp.Name())
|
||||||
|
}()
|
||||||
|
count := 0
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) {
|
||||||
|
count++
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("Logging 3 JSON messages")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"A\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
// Rotate the file, by renaming it
|
||||||
|
rotated := tmp.Name() + ".1"
|
||||||
|
os.Rename(tmp.Name(), rotated)
|
||||||
|
defer os.Remove(rotated)
|
||||||
|
// Open a new file, with the same name as before
|
||||||
|
f, err = os.OpenFile(tmp.Name(), os.O_WRONLY|os.O_CREATE, 0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("Logging 2 more JSON messages")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"D\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"E\"}")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
// Shut the mirroring down
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if count != 5 {
|
||||||
|
t.Fatalf("Expected 5 log entries; got %v", count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMirrorLogExistingFile(t *testing.T, newQM bool) int {
|
||||||
|
tmp, err := ioutil.TempFile("", t.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(tmp.Name())
|
||||||
|
log.Println("Logging 1 message before we start")
|
||||||
|
ioutil.WriteFile(tmp.Name(), []byte("{\"message\"=\"A\"}\n"), 0600)
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
count := 0
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
_, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string) {
|
||||||
|
count++
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(tmp.Name(), os.O_APPEND|os.O_WRONLY, 0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Logging 2 new JSON messages")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||||
|
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||||
|
f.Close()
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMirrorLogExistingFile tests that we only get new log messages, if the
|
||||||
|
// log file already exists
|
||||||
|
func TestMirrorLogExistingFile(t *testing.T) {
|
||||||
|
count := testMirrorLogExistingFile(t, false)
|
||||||
|
if count != 2 {
|
||||||
|
t.Fatalf("Expected 2 log entries; got %v", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMirrorLogExistingFileButNewQueueManager tests that we only get all log
|
||||||
|
// messages, even if the file exists, if we tell it we want all messages
|
||||||
|
func TestMirrorLogExistingFileButNewQueueManager(t *testing.T) {
|
||||||
|
count := testMirrorLogExistingFile(t, true)
|
||||||
|
if count != 3 {
|
||||||
|
t.Fatalf("Expected 3 log entries; got %v", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMirrorLogCancelWhileWaiting(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
_, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string) {
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
// No need to assert anything. If it didn't work, the code would have hung (TODO: not ideal)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -17,27 +17,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ibm-messaging/mq-container/pkg/linux/capabilities"
|
"github.com/ibm-messaging/mq-container/internal/capabilities"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fsTypes contains file system identifier codes.
|
|
||||||
// This code will not compile on some operating systems - Linux only.
|
|
||||||
var fsTypes = map[int64]string{
|
|
||||||
0x61756673: "aufs",
|
|
||||||
0xef53: "ext",
|
|
||||||
0x6969: "nfs",
|
|
||||||
0x65735546: "fuse",
|
|
||||||
0x9123683e: "btrfs",
|
|
||||||
0x01021994: "tmpfs",
|
|
||||||
0x794c7630: "overlayfs",
|
|
||||||
}
|
|
||||||
|
|
||||||
func logBaseImage() error {
|
func logBaseImage() error {
|
||||||
buf, err := ioutil.ReadFile("/etc/os-release")
|
buf, err := ioutil.ReadFile("/etc/os-release")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,7 +72,7 @@ func readProc(filename string) (value string, err error) {
|
|||||||
func readMounts() error {
|
func readMounts() error {
|
||||||
all, err := readProc("/proc/mounts")
|
all, err := readProc("/proc/mounts")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error: Couldn't read /proc/mounts")
|
log.Print("Error: Couldn't read /proc/mounts")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := strings.Split(all, "\n")
|
lines := strings.Split(all, "\n")
|
||||||
@@ -102,43 +88,27 @@ func readMounts() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !detected {
|
if !detected {
|
||||||
log.Println("No volume detected. Persistent messages may be lost")
|
log.Print("No volume detected. Persistent messages may be lost")
|
||||||
} else {
|
} else {
|
||||||
checkFS("/mnt/mqm")
|
checkFS("/mnt/mqm")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFS(path string) {
|
|
||||||
statfs := &unix.Statfs_t{}
|
|
||||||
err := unix.Statfs(path, statfs)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t := fsTypes[statfs.Type]
|
|
||||||
switch t {
|
|
||||||
case "aufs", "overlayfs", "tmpfs":
|
|
||||||
log.Fatalf("Error: %v uses unsupported filesystem type %v", path, t)
|
|
||||||
default:
|
|
||||||
log.Printf("Detected %v has filesystem type '%v'", path, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logConfig() {
|
func logConfig() {
|
||||||
log.Printf("CPU architecture: %v", runtime.GOARCH)
|
log.Printf("CPU architecture: %v", runtime.GOARCH)
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
var err error
|
var err error
|
||||||
osr, err := readProc("/proc/sys/kernel/osrelease")
|
osr, err := readProc("/proc/sys/kernel/osrelease")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Linux kernel version: %v", osr)
|
log.Printf("Linux kernel version: %v", osr)
|
||||||
}
|
}
|
||||||
logBaseImage()
|
logBaseImage()
|
||||||
fileMax, err := readProc("/proc/sys/fs/file-max")
|
fileMax, err := readProc("/proc/sys/fs/file-max")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Maximum file handles: %v", fileMax)
|
log.Printf("Maximum file handles: %v", fileMax)
|
||||||
}
|
}
|
||||||
|
|||||||
50
cmd/runmqserver/mqconfig_linux.go
Normal file
50
cmd/runmqserver/mqconfig_linux.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fsTypes contains file system identifier codes.
|
||||||
|
// This code will not compile on some operating systems - Linux only.
|
||||||
|
var fsTypes = map[int64]string{
|
||||||
|
0x61756673: "aufs",
|
||||||
|
0xef53: "ext",
|
||||||
|
0x6969: "nfs",
|
||||||
|
0x65735546: "fuse",
|
||||||
|
0x9123683e: "btrfs",
|
||||||
|
0x01021994: "tmpfs",
|
||||||
|
0x794c7630: "overlayfs",
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFS(path string) {
|
||||||
|
statfs := &unix.Statfs_t{}
|
||||||
|
err := unix.Statfs(path, statfs)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := fsTypes[statfs.Type]
|
||||||
|
switch t {
|
||||||
|
case "aufs", "overlayfs", "tmpfs":
|
||||||
|
log.Fatalf("Error: %v uses unsupported filesystem type %v", path, t)
|
||||||
|
default:
|
||||||
|
log.Printf("Detected %v has filesystem type '%v'", path, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
24
cmd/runmqserver/mqconfig_other.go
Normal file
24
cmd/runmqserver/mqconfig_other.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// Dummy version of this function, only for non-Linux systems.
|
||||||
|
// Having this allows unit tests to be run on other platforms (e.g. macOS)
|
||||||
|
func checkFS(path string) {
|
||||||
|
return
|
||||||
|
}
|
||||||
36
cmd/runmqserver/post_init_dev.go
Normal file
36
cmd/runmqserver/post_init_dev.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// +build mqdev
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
// postInit is run after /var/mqm is set up
|
||||||
|
// This version of postInit is only included as part of the MQ Advanced for Developers build
|
||||||
|
func postInit(name string) error {
|
||||||
|
disable := os.Getenv("MQ_DISABLE_WEB_CONSOLE")
|
||||||
|
if disable != "true" && disable != "1" {
|
||||||
|
// Configure and start the web server, in the background (if installed)
|
||||||
|
// WARNING: No error handling or health checking available for the web server,
|
||||||
|
// which is why it's limited to use with MQ Advanced for Developers only
|
||||||
|
go func() {
|
||||||
|
configureWebServer()
|
||||||
|
startWebServer()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
22
cmd/runmqserver/post_init_other.go
Normal file
22
cmd/runmqserver/post_init_other.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// +build !mqdev
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 postInit(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
132
cmd/runmqserver/qmgr.go
Normal file
132
cmd/runmqserver/qmgr.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createDirStructure creates the default MQ directory structure under /var/mqm
|
||||||
|
func createDirStructure() error {
|
||||||
|
out, _, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-s")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error creating directory structure: %v\n", string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Created directory structure under /var/mqm")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createQueueManager creates a queue manager, if it doesn't already exist.
|
||||||
|
// It returns true if one was created, or false if one already existed
|
||||||
|
func createQueueManager(name string) (bool, error) {
|
||||||
|
log.Printf("Creating queue manager %v", 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("Detected existing queue manager %v", name)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
log.Printf("crtmqm returned %v", rc)
|
||||||
|
log.Println(string(out))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCommandLevel() error {
|
||||||
|
level, ok := os.LookupEnv("MQ_CMDLEVEL")
|
||||||
|
if ok && level != "" {
|
||||||
|
log.Printf("Setting CMDLEVEL to %v", level)
|
||||||
|
out, rc, err := command.Run("strmqm", "-e", "CMDLEVEL="+level)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error %v setting CMDLEVEL: %v", rc, string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startQueueManager() error {
|
||||||
|
log.Println("Starting queue manager")
|
||||||
|
out, rc, err := command.Run("strmqm")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error %v starting queue manager: %v", rc, string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Started queue manager")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureQueueManager() error {
|
||||||
|
const configDir string = "/etc/mqm"
|
||||||
|
files, err := ioutil.ReadDir(configDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if strings.HasSuffix(file.Name(), ".mqsc") {
|
||||||
|
abs := filepath.Join(configDir, file.Name())
|
||||||
|
cmd := exec.Command("runmqsc")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Open the MQSC file for reading
|
||||||
|
f, err := os.Open(abs)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error opening %v: %v", abs, err)
|
||||||
|
}
|
||||||
|
// Copy the contents to stdin of the runmqsc process
|
||||||
|
_, err = io.Copy(stdin, f)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading %v: %v", abs, err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
stdin.Close()
|
||||||
|
// Run the command and wait for completion
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
// Print the runmqsc output, adding tab characters to make it more readable as part of the log
|
||||||
|
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, strings.Replace(string(out), "\n", "\n\t", -1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopQueueManager(name string) error {
|
||||||
|
log.Println("Stopping queue manager")
|
||||||
|
out, _, err := command.Run("endmqm", "-w", name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error stopping queue manager: %v", string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Stopped queue manager")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
81
cmd/runmqserver/signals.go
Normal file
81
cmd/runmqserver/signals.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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"
|
||||||
|
"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:
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
logDebugf("Reaped PID %v", pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
110
cmd/runmqserver/webserver.go
Normal file
110
cmd/runmqserver/webserver.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// +build mqdev
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startWebServer() error {
|
||||||
|
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
log.Debug("Skipping web server, because it's not installed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Println("Starting web server")
|
||||||
|
out, rc, err := command.RunAsMQM("strmqweb")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error %v starting web server: %v", rc, string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Started web server")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureWebServer() error {
|
||||||
|
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
const webConfigDir string = "/etc/mqm/web"
|
||||||
|
_, err = os.Stat(webConfigDir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uid, gid, err := lookupMQM()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
const prefix string = "/etc/mqm/web"
|
||||||
|
err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to := fmt.Sprintf("/var/mqm/web%v", from[len(prefix):])
|
||||||
|
exists := true
|
||||||
|
_, err = os.Stat(to)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
exists = false
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
if !exists {
|
||||||
|
err := os.MkdirAll(to, 0770)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Directory: %v --> %v", from, to)
|
||||||
|
} else {
|
||||||
|
if exists {
|
||||||
|
err := os.Remove(to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Permissions. Can't rely on them being set in Dockerfile
|
||||||
|
err := os.Link(from, to)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("File: %v", from)
|
||||||
|
}
|
||||||
|
err = os.Chown(to, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
43
docs/building.md
Normal file
43
docs/building.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Building a Docker image
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
You need to ensure you have the following tools installed:
|
||||||
|
* [Docker](https://www.docker.com/) V17.06.1 or later
|
||||||
|
* [GNU make](https://www.gnu.org/software/make/)
|
||||||
|
|
||||||
|
## Building a production image
|
||||||
|
This procedure works for building the MQ Continuous Delivery release, on `x86_64`, `ppc64le` and `s390x` architectures.
|
||||||
|
|
||||||
|
1. Create a `downloads` directory in the root of this repository
|
||||||
|
2. Download MQ from IBM Passport Advantage, and place the downloaded file (for example, `IBM_MQ_9.0.4.0_UBUNTU_X86-64.tar.gz` for MQ V9.0.4 for Ubuntu on x86_64 architecture) in the `downloads` directory
|
||||||
|
2. Run `make build-advancedserver`
|
||||||
|
|
||||||
|
> **Warning**: Note that MQ offers two different sets of packaging on Linux: one is called "MQ for Linux" and contains RPM files for installing on Red Hat Enterprise Linux and SUSE Linux Enterprise Server. The other package is called "MQ for Ubuntu", and contains DEB files for installing on Ubuntu.
|
||||||
|
|
||||||
|
You can build a different version of MQ by setting the `MQ_VERSION` environment variable, for example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MQ_VERSION=9.0.4.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 make build-advancedserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building a developer image
|
||||||
|
Run `make build-devserver`, which will download the latest version of MQ Advanced for Developers from IBM developerWorks. This is currently only available on the `x86_64` architecture.
|
||||||
|
|
||||||
|
You can use the environment variable `MQ_ARCHIVE_DEV` to specify an alternative local file to install from (which must be in the `downloads` directory).
|
||||||
|
|
||||||
|
## Building on a different base image
|
||||||
|
By default, the MQ images use Ubuntu as the base layer. You can build using a Red Hat Enterprise Linux compatible base layer by setting the `BASE_IMAGE` environment variable. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
BASE_IMAGE=centos:7 make build-advancedserver
|
||||||
|
```
|
||||||
|
|
||||||
|
The `make` tool will try and locate the right archive file under the `downloads` directory, based on your platform architecture and your `MQ_VERSION` environment variable, for example `IBM_MQ_9.0.4.0_LINUX_X86_64.tar.gz` for MQ V9.0.4.0 on x86_64. You can also set the `MQ_ARCHIVE` environment variable to set the specific file name.
|
||||||
|
|
||||||
|
Note that if you are using Red Hat Enterprise Linux, you will need to create your own base image layer, with your subscription enabled, as described [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/getting_started_with_containers/get_started_with_docker_formatted_container_images). The MQ image build needs to install some additional packages, and a subscription is required to access the Red Hat repositories.
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Developing
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
You need to ensure you have the following tools installed:
|
|
||||||
|
|
||||||
* [Docker](https://www.docker.com/)
|
|
||||||
* [Go](https://golang.org/)
|
|
||||||
* [Glide](https://glide.sh/)
|
|
||||||
* [dep](https://github.com/golang/dep) (official Go dependency management tool)
|
|
||||||
* make
|
|
||||||
|
|
||||||
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/).
|
|
||||||
|
|
||||||
## Running the tests
|
|
||||||
There are three main sets of tests:
|
|
||||||
|
|
||||||
1. Unit tests
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make test-devserver
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running the Kubernetes tests
|
|
||||||
|
|
||||||
For the Kubernetes tests, you need to have built the Docker image, and pushed it to the registry used by your Kubernetes cluster. Most of the configuration used by the tests is picked up from your `kubectl` configuration, but you will typically need to specify the image details. For example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOCKER_REPO_DEVSERVER=mycluster.icp:8500/default/mq-devserver make test-kubernetes-devserver
|
|
||||||
```
|
|
||||||
25
docs/internals.md
Normal file
25
docs/internals.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Internals
|
||||||
|
|
||||||
|
This page documents internal code details and design decisions.
|
||||||
|
|
||||||
|
The resulting Docker image contains the following:
|
||||||
|
|
||||||
|
* Base linux distribution - this provides standard Linux libraries (such as "glibc") and utilities (such as "ls" and "grep") required by MQ
|
||||||
|
* MQ installation (under `/opt/mqm`)
|
||||||
|
* Three additional programs, to enable running in a containerized environment:
|
||||||
|
- `runmqserver` - The main process, which creates and runs a queue manager
|
||||||
|
- `chkmqhealthy` - Checks the health of the queue manager. This can be used by (say) a Kubernetes liveness probe.
|
||||||
|
- `chkmqready` - Checks if the queue manager is ready for work. This can be used by (say) a Kubernetes readiness probe.
|
||||||
|
|
||||||
|
## runmqserver
|
||||||
|
The `runmqserver` command has the following responsibilities:
|
||||||
|
|
||||||
|
* Checks license acceptance
|
||||||
|
* Sets up `/var/mqm`
|
||||||
|
- MQ data directory needs to be set up at container creation time. This is done using the `crtmqdir` utility, which was introduced in MQ V9.0.3
|
||||||
|
- It assumes that a storage volume for data is mounted under `/mnt/mqm`. It creates a sub-directory for the MQ data, so `/var/mqm` is a symlink which resolves to `/mnt/mqm/data`. The reason for this is that it's not always possible to change the ownership of an NFS mount point directly (`/var/mqm` needs to be owned by "mqm"), but you can change the ownership of a sub-directory.
|
||||||
|
* Acts like a daemon
|
||||||
|
- Handles UNIX signals, like SIGTERM
|
||||||
|
- Works as PID 1, so is responsible for [reaping zombie processes](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)
|
||||||
|
* Creating and starting a queue manager
|
||||||
|
* Configuring the queue manager, by running any MQSC scripts found under `/etc/mqm`
|
||||||
70
docs/testing.md
Normal file
70
docs/testing.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
You need to ensure you have the following tools installed:
|
||||||
|
* [Docker](https://www.docker.com/)
|
||||||
|
* [GNU make](https://www.gnu.org/software/make/)
|
||||||
|
* [Go](https://golang.org/) - only needed for running the tests
|
||||||
|
* [dep](https://github.com/golang/dep) (official Go dependency management tool) - 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/).
|
||||||
|
|
||||||
|
## Preparing to run the tests
|
||||||
|
The test dependencies are not included with the source code, so you need to download them before you can run them. This can be done with the following command, which uses the `dep` tool:
|
||||||
|
|
||||||
|
```
|
||||||
|
make deps
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
There are three main sets of 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 Docker tests
|
||||||
|
The Docker tests can be run locally on a machine with Docker. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
make test-devserver
|
||||||
|
make test-advancedserver
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify the image to use directly by using the `MQ_IMAGE_ADVANCEDSERVER` or `MQ_IMAGE_DEVSERVER` variables, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
MQ_IMAGE_ADVANCEDSERVER=mqadvanced-server9.0.4.0-x86_64-ubuntu-16.04 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
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the same environment variables you specified when [building](./building), for example, the following will try and test an image called `mqadvanced-server9.0.3.0-x86_64-ubuntu-16.04`:
|
||||||
|
|
||||||
|
```
|
||||||
|
MQ_VERSION=9.0.3.0 make test-advancedserver
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Docker tests with code coverage
|
||||||
|
You can produce code coverage results from the Docker tests by running the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
make build-advancedserver-cover
|
||||||
|
make test-advancedserver-cover
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to generate code coverage metrics from the Docker tests, the build step creates a new Docker image with an instrumented version of the code. Each test is then run individually, producing a coverage report each under `test/docker/coverage/`. These individual reports are then combined. The combined report is written to the `coverage` directory.
|
||||||
|
|
||||||
|
|
||||||
|
### Running the Kubernetes tests
|
||||||
|
|
||||||
|
For the Kubernetes tests, you need to have built the Docker image, and pushed it to the registry used by your Kubernetes cluster. Most of the configuration used by the tests is picked up from your `kubectl` configuration, but you will typically need to specify the image details. For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MQ_IMAGE=mycluster.icp:8500/default/mq-devserver make test-kubernetes-devserver
|
||||||
|
```
|
||||||
52
glide.lock
generated
52
glide.lock
generated
@@ -1,19 +1,47 @@
|
|||||||
hash: 170f17bb34eaa2c23733b3919e76a268654e50a21e70b45e1b7b2dd2161efc67
|
hash: 4905ad6acf8e593e3c4432f31dd61275b16d0b5b95f438471749ae1943af785d
|
||||||
updated: 2017-09-25T16:58:43.624633314+01:00
|
updated: 2018-02-22T17:26:13.366677Z
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/hpcloud/tail
|
- name: golang.org/x/crypto
|
||||||
version: a30252cb686a21eb2d0b98132633053ec2f7f1e5
|
version: 81e90905daefcd6fd217b62423c0908922eadb30
|
||||||
subpackages:
|
subpackages:
|
||||||
- ratelimiter
|
- acme
|
||||||
- util
|
- acme/autocert
|
||||||
- watch
|
- blowfish
|
||||||
- winfile
|
- cast5
|
||||||
|
- chacha20poly1305/internal/chacha20
|
||||||
|
- cryptobyte
|
||||||
|
- curve25519
|
||||||
|
- ed25519
|
||||||
|
- ed25519/internal/edwards25519
|
||||||
|
- hkdf
|
||||||
|
- md4
|
||||||
|
- nacl/auth
|
||||||
|
- nacl/box
|
||||||
|
- nacl/secretbox
|
||||||
|
- openpgp
|
||||||
|
- openpgp/armor
|
||||||
|
- openpgp/elgamal
|
||||||
|
- openpgp/errors
|
||||||
|
- openpgp/packet
|
||||||
|
- openpgp/s2k
|
||||||
|
- pbkdf2
|
||||||
|
- pkcs12/internal/rc2
|
||||||
|
- poly1305
|
||||||
|
- ripemd160
|
||||||
|
- salsa20/salsa
|
||||||
|
- ssh
|
||||||
|
- ssh/agent
|
||||||
|
- ssh/terminal
|
||||||
|
- ssh/testdata
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: 7a4fde3fda8ef580a89dbae8138c26041be14299
|
version: 7a4fde3fda8ef580a89dbae8138c26041be14299
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- plan9
|
||||||
- unix
|
- unix
|
||||||
- name: gopkg.in/fsnotify.v1
|
- windows
|
||||||
version: 7be54206639f256967dd82fa767397ba5f8f48f5
|
- windows/registry
|
||||||
- name: gopkg.in/tomb.v1
|
- windows/svc
|
||||||
version: c131134a1947e9afd9cecfe11f4c6dff0732ae58
|
- windows/svc/debug
|
||||||
|
- windows/svc/eventlog
|
||||||
|
- windows/svc/mgr
|
||||||
testImports: []
|
testImports: []
|
||||||
|
|||||||
@@ -19,6 +19,4 @@ excludeDirs:
|
|||||||
- coverage
|
- coverage
|
||||||
- test
|
- test
|
||||||
import:
|
import:
|
||||||
- package: github.com/hpcloud/tail
|
|
||||||
version: v1.0.0
|
|
||||||
- package: golang.org/x/sys/unix
|
- package: golang.org/x/sys/unix
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
|
||||||
Docker for Mac
|
Docker for Mac
|
||||||
--------------
|
--------------
|
||||||
|
Steps to build a Docker image containing IBM MQ Explorer:
|
||||||
|
1. Download and extract the code from [GitHub](https://codeload.github.com/ibm-messaging/mq-container/zip/master), or run the following command: `git clone https://github.com/ibm-messaging/mq-container`
|
||||||
|
2. Open a Terminal window in the `mq-container` directory
|
||||||
|
3. Run `docker build -t mq-explorer -f ./incubating/mq-explorer/Dockerfile .`
|
||||||
|
|
||||||
1. Install XQuartz. Version 2.7.10 works, but V2.7.11 doesn't seem to.
|
Steps to prepare your Mac with XQuartz:
|
||||||
|
1. Install XQuartz. Version 2.7.10 works, but V2.7.11 doesn't seem to (see [this thread](https://stackoverflow.com/questions/38686932/how-to-forward-docker-for-mac-to-x11))
|
||||||
2. Run XQuartz
|
2. Run XQuartz
|
||||||
3. Open the XQuartz "Preferences" menu, go to the "Security" tab and enable "Allow connections from network clients"
|
3. Open the XQuartz "Preferences" menu, go to the "Security" tab and enable "Allow connections from network clients"
|
||||||
4. Add your IP address to the list of allowed hosts: `xhost + $(ipconfig getifaddr en0)`
|
|
||||||
5. Run MQ Explorer: `docker run -e DISPLAY=$(ipconfig getifaddr en0):0 -v /tmp/.X11-unix:/tmp/.X11-unix -u 0 -ti mq-explorer`
|
|
||||||
|
|
||||||
https://stackoverflow.com/questions/38686932/how-to-forward-docker-for-mac-to-x11
|
Steps to run IBM MQ Explorer:
|
||||||
|
1. Add your IP address to the list of allowed hosts: `xhost + $(ipconfig getifaddr en0)`
|
||||||
|
2. Run MQ Explorer: `docker run -e DISPLAY=$(ipconfig getifaddr en0):0 -v /tmp/.X11-unix:/tmp/.X11-unix -u 0 -ti mq-explorer`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
docker run -e DISPLAY=docker.for.mac.localhost:0 -v /tmp/.X11-unix:/tmp/.X11-unix -u 0 -ti mq-explorer
|
|
||||||
Use DISPLAY=docker.for.mac.localhost:0 ???
|
|
||||||
|
|||||||
56
incubating/mqadvanced-server-dev/Dockerfile
Normal file
56
incubating/mqadvanced-server-dev/Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# © Copyright IBM Corporation 2015, 2018
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# 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 internal/ ./internal
|
||||||
|
COPY vendor/ ./vendor
|
||||||
|
# Re-build runmqserver, with code tagged with 'mqdev' enabled
|
||||||
|
RUN go build --tags 'mqdev' ./cmd/runmqserver
|
||||||
|
RUN go build ./cmd/runmqdevserver/
|
||||||
|
# Run all unit tests
|
||||||
|
RUN go test -v ./cmd/runmqdevserver/...
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Main build stage
|
||||||
|
###############################################################################
|
||||||
|
FROM mqadvanced-server-dev-base:9.0.4.0-x86_64-ubuntu-16.04
|
||||||
|
|
||||||
|
# Enable MQ developer default configuration
|
||||||
|
ENV MQ_DEV=true
|
||||||
|
|
||||||
|
# Default administrator password
|
||||||
|
ENV MQ_ADMIN_PASSWORD=passw0rd
|
||||||
|
|
||||||
|
## Add admin and app users, and set a default password for admin
|
||||||
|
RUN useradd admin -G mqm \
|
||||||
|
&& groupadd mqclient \
|
||||||
|
&& useradd app -G mqclient,mqm \
|
||||||
|
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
|
||||||
|
|
||||||
|
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
||||||
|
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
|
||||||
|
# Copy template MQSC for default developer configuration
|
||||||
|
COPY incubating/mqadvanced-server-dev/dev.mqsc.tpl /etc/mqm/
|
||||||
|
# Copy web XML files for default developer configuration
|
||||||
|
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
|
||||||
|
RUN chmod +x /usr/local/bin/runmq*
|
||||||
|
|
||||||
|
EXPOSE 9443
|
||||||
|
|
||||||
|
ENTRYPOINT ["runmqdevserver"]
|
||||||
53
incubating/mqadvanced-server-dev/dev.mqsc.tpl
Normal file
53
incubating/mqadvanced-server-dev/dev.mqsc.tpl
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
* © Copyright IBM Corporation 2017, 2018
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
STOP LISTENER('SYSTEM.DEFAULT.LISTENER.TCP')
|
||||||
|
|
||||||
|
* Developer queues
|
||||||
|
DEFINE QLOCAL('DEV.QUEUE.1') REPLACE
|
||||||
|
DEFINE QLOCAL('DEV.QUEUE.2') REPLACE
|
||||||
|
DEFINE QLOCAL('DEV.QUEUE.3') REPLACE
|
||||||
|
DEFINE QLOCAL('DEV.DEAD.LETTER.QUEUE') REPLACE
|
||||||
|
|
||||||
|
* Use a different dead letter queue, for undeliverable messages
|
||||||
|
ALTER QMGR DEADQ('DEV.DEAD.LETTER.QUEUE')
|
||||||
|
|
||||||
|
* Developer topics
|
||||||
|
DEFINE TOPIC('DEV.BASE.TOPIC') TOPICSTR('dev/') REPLACE
|
||||||
|
|
||||||
|
* Developer connection authentication
|
||||||
|
DEFINE AUTHINFO('DEV.AUTHINFO') AUTHTYPE(IDPWOS) CHCKCLNT(REQDADM) CHCKLOCL(OPTIONAL) ADOPTCTX(YES) REPLACE
|
||||||
|
ALTER QMGR CONNAUTH('DEV.AUTHINFO')
|
||||||
|
REFRESH SECURITY(*) TYPE(CONNAUTH)
|
||||||
|
|
||||||
|
* Developer channels (Application + Admin)
|
||||||
|
DEFINE CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) REPLACE
|
||||||
|
DEFINE CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) REPLACE
|
||||||
|
|
||||||
|
* Developer channel authentication rules
|
||||||
|
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('Back-stop rule - Blocks everyone') ACTION(REPLACE)
|
||||||
|
SET CHLAUTH('DEV.APP.SVRCONN') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(CHANNEL) CHCKCLNT({{ .ChckClnt }}) DESCR('Allows connection via APP channel') ACTION(REPLACE)
|
||||||
|
SET CHLAUTH('DEV.ADMIN.SVRCONN') TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allows admins on ADMIN channel') ACTION(REPLACE)
|
||||||
|
SET CHLAUTH('DEV.ADMIN.SVRCONN') TYPE(USERMAP) CLNTUSER('admin') USERSRC(CHANNEL) DESCR('Allows admin user to connect via ADMIN channel') ACTION(REPLACE)
|
||||||
|
|
||||||
|
* Developer authority records
|
||||||
|
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('root') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
|
||||||
|
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('mqm') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
|
||||||
|
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(QUEUE) AUTHADD(BROWSE,GET,INQ,PUT)
|
||||||
|
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)
|
||||||
|
|
||||||
|
* Developer listener
|
||||||
|
DEFINE LISTENER('DEV.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE
|
||||||
|
START LISTENER('DEV.LISTENER.TCP')
|
||||||
17
incubating/mqadvanced-server-dev/entrypoint.sh
Normal file
17
incubating/mqadvanced-server-dev/entrypoint.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Change admin password
|
||||||
|
if [ -n "${MQ_ADMIN_PASSWORD}" ]; then
|
||||||
|
echo admin:${MQ_ADMIN_PASSWORD} | chpasswd
|
||||||
|
fi
|
||||||
|
# Change app password
|
||||||
|
if [ -n "${MQ_APP_PASSWORD}" ]; then
|
||||||
|
echo app:${MQ_APP_PASSWORD} | chpasswd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete the MQSC with developer defaults, if requested
|
||||||
|
if [ "${MQ_DEV}" != "true" ]; then
|
||||||
|
rm -f /etc/mqm/dev.mqsc
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec runmqserver
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<server>
|
||||||
|
<featureManager>
|
||||||
|
<feature>appSecurity-2.0</feature>
|
||||||
|
<feature>basicAuthenticationMQ-1.0</feature>
|
||||||
|
</featureManager>
|
||||||
|
<enterpriseApplication id="com.ibm.mq.console">
|
||||||
|
<application-bnd>
|
||||||
|
<security-role name="MQWebAdmin">
|
||||||
|
<group name="MQWebUI" realm="defaultRealm"/>
|
||||||
|
</security-role>
|
||||||
|
</application-bnd>
|
||||||
|
</enterpriseApplication>
|
||||||
|
<enterpriseApplication id="com.ibm.mq.rest">
|
||||||
|
<application-bnd>
|
||||||
|
<security-role name="MQWebAdmin">
|
||||||
|
<group name="MQWebUI" realm="defaultRealm"/>
|
||||||
|
</security-role>
|
||||||
|
</application-bnd>
|
||||||
|
</enterpriseApplication>
|
||||||
|
<basicRegistry id="basic" realm="defaultRealm">
|
||||||
|
<user name="admin" password="${env.MQ_ADMIN_PASSWORD}"/>
|
||||||
|
<group name="MQWebUI">
|
||||||
|
<member name="admin"/>
|
||||||
|
</group>
|
||||||
|
</basicRegistry>
|
||||||
|
<variable name="httpHost" value="*"/>
|
||||||
|
<httpDispatcher enableWelcomePage="false" appOrContextRootMissingMessage='Redirecting to console.<script>document.location.href="/ibmmq/console";</script>' />
|
||||||
|
<include location="tls.xml"/>
|
||||||
|
</server>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<keyStore id="MQWebKeyStore" location="/var/mqm/web/installations/Installation1/servers/mqweb/key.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
||||||
|
<keyStore id="MQWebTrustStore" location="/var/mqm/web/installations/Installation1/servers/mqweb/trust.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
||||||
|
<ssl id="thisSSLConfig" clientAuthenticationSupported="true" keyStoreRef="MQWebKeyStore" trustStoreRef="MQWebTrustStore" sslProtocol="TLSv1.2" serverKeyAlias="webcert"/>
|
||||||
|
<sslDefault sslRef="thisSSLConfig"/>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<sslDefault sslRef="mqDefaultSSLConfig"/>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# -*- mode: sh -*-
|
# -*- mode: sh -*-
|
||||||
# © Copyright IBM Corporation 2015, 2017
|
# © Copyright IBM Corporation 2015, 2018
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -18,61 +18,102 @@
|
|||||||
# Fail on any non-zero return code
|
# Fail on any non-zero return code
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
test -f /usr/bin/yum && RHEL=true || RHEL=false
|
||||||
|
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
|
||||||
|
|
||||||
|
# If MQ_PACKAGES isn't specifically set, then choose a valid set of defaults
|
||||||
|
if [ -z "$MQ_PACKAGES" ]; then
|
||||||
|
$UBUNTU && MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams"
|
||||||
|
$RHEL && MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ($UBUNTU); then
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
# Use a reduced set of apt repositories.
|
||||||
|
# This ensures no unsupported code gets installed, and makes the build faster
|
||||||
|
source /etc/os-release
|
||||||
|
echo "deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list
|
||||||
|
echo "deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list
|
||||||
|
echo "deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list
|
||||||
|
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
bash \
|
||||||
|
bc \
|
||||||
|
ca-certificates \
|
||||||
|
coreutils \
|
||||||
|
curl \
|
||||||
|
debianutils \
|
||||||
|
file \
|
||||||
|
findutils \
|
||||||
|
gawk \
|
||||||
|
grep \
|
||||||
|
libc-bin \
|
||||||
|
mount \
|
||||||
|
passwd \
|
||||||
|
procps \
|
||||||
|
sed \
|
||||||
|
tar \
|
||||||
|
util-linux
|
||||||
|
fi
|
||||||
|
|
||||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||||
apt-get update
|
$RHEL && yum -y install \
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
bash \
|
bash \
|
||||||
bc \
|
bc \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
coreutils \
|
coreutils \
|
||||||
curl \
|
curl \
|
||||||
debianutils \
|
|
||||||
file \
|
file \
|
||||||
findutils \
|
findutils \
|
||||||
gawk \
|
gawk \
|
||||||
|
glibc-common \
|
||||||
grep \
|
grep \
|
||||||
libc-bin \
|
|
||||||
mount \
|
|
||||||
passwd \
|
passwd \
|
||||||
procps \
|
procps-ng \
|
||||||
sed \
|
sed \
|
||||||
tar \
|
tar \
|
||||||
util-linux
|
util-linux
|
||||||
|
|
||||||
# Download and extract the MQ installation files
|
# Download and extract the MQ installation files
|
||||||
DIR_EXTRACT=/tmp/mq
|
DIR_EXTRACT=/tmp/mq
|
||||||
mkdir -p ${DIR_EXTRACT}
|
mkdir -p ${DIR_EXTRACT}
|
||||||
cd ${DIR_EXTRACT}
|
cd ${DIR_EXTRACT}
|
||||||
curl -LO $MQ_URL
|
curl -LO $MQ_URL
|
||||||
tar -zxvf ./*.tar.gz
|
tar -zxvf ./*.tar.gz
|
||||||
|
|
||||||
# Remove packages only needed by this script
|
# Remove packages only needed by this script
|
||||||
apt-get purge -y \
|
$UBUNTU && apt-get purge -y \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl
|
curl
|
||||||
|
|
||||||
|
# Note: ca-certificates and curl are installed by default in RHEL
|
||||||
|
|
||||||
# Remove any orphaned packages
|
# Remove any orphaned packages
|
||||||
apt-get autoremove -y
|
$UBUNTU && apt-get autoremove -y
|
||||||
|
|
||||||
# Recommended: Create the mqm user ID with a fixed UID and group, so that the file permissions work between different images
|
# Recommended: Create the mqm user ID with a fixed UID and group, so that the file permissions work between different images
|
||||||
groupadd --system --gid 999 mqm
|
$UBUNTU && groupadd --system --gid 999 mqm
|
||||||
useradd --system --uid 999 --gid mqm mqm
|
$UBUNTU && useradd --system --uid 999 --gid mqm mqm
|
||||||
|
$RHEL && groupadd --system --gid 888 mqm
|
||||||
|
$RHEL && useradd --system --uid 888 --gid mqm mqm
|
||||||
usermod -G mqm root
|
usermod -G mqm root
|
||||||
|
|
||||||
# Find directory containing .deb files
|
# Find directory containing .deb files
|
||||||
DIR_DEB=$(find ${DIR_EXTRACT} -name "*.deb" -printf "%h\n" | sort -u | head -1)
|
$UBUNTU && DIR_DEB=$(find ${DIR_EXTRACT} -name "*.deb" -printf "%h\n" | sort -u | head -1)
|
||||||
|
$RHEL && DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
|
||||||
# Find location of mqlicense.sh
|
# Find location of mqlicense.sh
|
||||||
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
|
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
|
||||||
|
|
||||||
# Accept the MQ license
|
# Accept the MQ license
|
||||||
${MQLICENSE} -text_only -accept
|
${MQLICENSE} -text_only -accept
|
||||||
echo "deb [trusted=yes] file:${DIR_DEB} ./" > /etc/apt/sources.list.d/IBM_MQ.list
|
$UBUNTU && echo "deb [trusted=yes] file:${DIR_DEB} ./" > /etc/apt/sources.list.d/IBM_MQ.list
|
||||||
|
|
||||||
# Install MQ using the DEB packages
|
# Install MQ using the DEB packages
|
||||||
apt-get update
|
$UBUNTU && apt-get update
|
||||||
apt-get install -y $MQ_PACKAGES
|
$UBUNTU && apt-get install -y $MQ_PACKAGES
|
||||||
|
|
||||||
|
$RHEL && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES
|
||||||
|
|
||||||
# Remove 32-bit libraries from 64-bit container
|
# Remove 32-bit libraries from 64-bit container
|
||||||
find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
|
find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
|
||||||
@@ -84,21 +125,21 @@ find /opt/mqm -name '*.tar.gz' -delete
|
|||||||
/opt/mqm/bin/setmqinst -p /opt/mqm -i
|
/opt/mqm/bin/setmqinst -p /opt/mqm -i
|
||||||
|
|
||||||
# Clean up all the downloaded files
|
# Clean up all the downloaded files
|
||||||
rm -f /etc/apt/sources.list.d/IBM_MQ.list
|
$UBUNTU && rm -f /etc/apt/sources.list.d/IBM_MQ.list
|
||||||
rm -rf ${DIR_EXTRACT}
|
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
|
# 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
|
$UBUNTU && apt-get upgrade -y sensible-utils
|
||||||
apt-get upgrade -y libexpat1
|
|
||||||
|
|
||||||
# End of bug fixes
|
# End of bug fixes
|
||||||
|
|
||||||
# Clean up cached apt files
|
# Clean up cached files
|
||||||
rm -rf /var/lib/apt/lists/*
|
$UBUNTU && rm -rf /var/lib/apt/lists/*
|
||||||
|
$RHEL && yum -y clean all
|
||||||
|
$RHEL && rm -rf /var/cache/yum/*
|
||||||
|
|
||||||
# Optional: Update the command prompt with the MQ version
|
# Optional: Update the command prompt with the MQ version
|
||||||
echo "mq:$(dspmqver -b -f 2)" > /etc/debian_chroot
|
$UBUNTU && echo "mq:$(dspmqver -b -f 2)" > /etc/debian_chroot
|
||||||
|
|
||||||
# Remove the directory structure under /var/mqm which was created by the installer
|
# Remove the directory structure under /var/mqm which was created by the installer
|
||||||
rm -rf /var/mqm
|
rm -rf /var/mqm
|
||||||
@@ -115,4 +156,7 @@ ln -s /mnt/mqm/data /var/mqm
|
|||||||
# Optional: Set these values for the Bluemix Vulnerability Report
|
# Optional: Set these values for the Bluemix Vulnerability Report
|
||||||
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' /etc/login.defs
|
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' /etc/login.defs
|
||||||
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' /etc/login.defs
|
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' /etc/login.defs
|
||||||
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' /etc/pam.d/common-password
|
|
||||||
|
$UBUNTU && PAM_FILE=/etc/pam.d/common-password
|
||||||
|
$RHEL && PAM_FILE=/etc/pam.d/password-auth
|
||||||
|
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $PAM_FILE
|
||||||
|
|||||||
86
internal/command/command.go
Normal file
86
internal/command/command.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunCmd 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 RunCmd(cmd *exec.Cmd) (string, int, error) {
|
||||||
|
// 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(), fmt.Errorf("%v: %v", cmd.Path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out), -1, err
|
||||||
|
}
|
||||||
|
return string(out), 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return RunCmd(exec.Command(name, arg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAsMQM runs the specified command as the mqm user
|
||||||
|
func RunAsMQM(name string, arg ...string) (string, int, error) {
|
||||||
|
cmd := exec.Command(name, arg...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
uid, gid, err := lookupMQM()
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||||
|
return RunCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Duplicated code
|
||||||
|
func lookupMQM() (int, int, error) {
|
||||||
|
mqm, err := user.Lookup("mqm")
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
mqmUID, err := strconv.Atoi(mqm.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
mqmGID, err := strconv.Atoi(mqm.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return mqmUID, mqmGID, nil
|
||||||
|
}
|
||||||
47
internal/command/command_test.go
Normal file
47
internal/command/command_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
internal/logger/logger.go
Normal file
154
internal/logger/logger.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// timestampFormat matches the format used by MQ messages (includes milliseconds)
|
||||||
|
const timestampFormat string = "2006-01-02T15:04:05.000Z07:00"
|
||||||
|
const debugLevel string = "DEBUG"
|
||||||
|
const infoLevel string = "INFO"
|
||||||
|
const errorLevel string = "ERROR"
|
||||||
|
|
||||||
|
// A Logger is used to log messages to stdout
|
||||||
|
type Logger struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
writer io.Writer
|
||||||
|
debug bool
|
||||||
|
json bool
|
||||||
|
processName string
|
||||||
|
pid int
|
||||||
|
serverName string
|
||||||
|
host string
|
||||||
|
user *user.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger creates a new logger
|
||||||
|
func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Logger, error) {
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Logger{
|
||||||
|
mutex: sync.Mutex{},
|
||||||
|
writer: writer,
|
||||||
|
debug: debug,
|
||||||
|
json: json,
|
||||||
|
processName: os.Args[0],
|
||||||
|
pid: os.Getpid(),
|
||||||
|
serverName: serverName,
|
||||||
|
host: hostname,
|
||||||
|
user: user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) format(entry map[string]interface{}) (string, error) {
|
||||||
|
if l.json {
|
||||||
|
b, err := json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v %v\n", entry["ibm_datetime"], entry["message"]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// log logs a message at the specified level. The message is enriched with
|
||||||
|
// additional fields.
|
||||||
|
func (l *Logger) log(level string, msg string) {
|
||||||
|
t := time.Now()
|
||||||
|
entry := map[string]interface{}{
|
||||||
|
"message": fmt.Sprint(msg),
|
||||||
|
"ibm_datetime": t.Format(timestampFormat),
|
||||||
|
"loglevel": level,
|
||||||
|
"host": l.host,
|
||||||
|
"ibm_serverName": l.serverName,
|
||||||
|
"ibm_processName": l.processName,
|
||||||
|
"ibm_processId": l.pid,
|
||||||
|
"ibm_userName": l.user.Username,
|
||||||
|
"type": "mq_containerlog",
|
||||||
|
}
|
||||||
|
s, err := l.format(entry)
|
||||||
|
l.mutex.Lock()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Fix this
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
if l.json {
|
||||||
|
fmt.Fprintln(l.writer, s)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(l.writer, s)
|
||||||
|
}
|
||||||
|
l.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) LogDirect(msg string) {
|
||||||
|
fmt.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(args ...interface{}) {
|
||||||
|
if l.debug {
|
||||||
|
l.log(debugLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
if l.debug {
|
||||||
|
l.log(debugLevel, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Print(args ...interface{}) {
|
||||||
|
l.log(infoLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Println(args ...interface{}) {
|
||||||
|
l.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
l.log(infoLevel, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) PrintString(msg string) {
|
||||||
|
l.log(infoLevel, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(args ...interface{}) {
|
||||||
|
l.log(errorLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
l.log(errorLevel, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this
|
||||||
|
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
l.log("FATAL", fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
55
internal/logger/logger_test.go
Normal file
55
internal/logger/logger_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSONLogger(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
l, err := NewLogger(buf, true, true, t.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := "Hello world"
|
||||||
|
l.Print(s)
|
||||||
|
var e map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(buf.String()), &e)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if s != e["message"] {
|
||||||
|
t.Errorf("Expected JSON to contain message=%v; got %v", s, buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleLogger(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
l, err := NewLogger(buf, true, false, t.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := "Hello world"
|
||||||
|
l.Print(s)
|
||||||
|
if !strings.Contains(buf.String(), s) {
|
||||||
|
t.Errorf("Expected log output to contain %v; got %v", s, buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
internal/mqini/dspmqinf1.txt
Normal file
5
internal/mqini/dspmqinf1.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
QueueManager:
|
||||||
|
Name=foo
|
||||||
|
Directory=foo
|
||||||
|
Prefix=/var/mqm
|
||||||
|
InstallationName=Installation1
|
||||||
5
internal/mqini/dspmqinf2.txt
Normal file
5
internal/mqini/dspmqinf2.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
QueueManager:
|
||||||
|
Name=a/b
|
||||||
|
Directory=a&b
|
||||||
|
Prefix=/var/mqm
|
||||||
|
InstallationName=Installation1
|
||||||
5
internal/mqini/dspmqinf3.txt
Normal file
5
internal/mqini/dspmqinf3.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
QueueManager:
|
||||||
|
Name=..
|
||||||
|
Directory=!!
|
||||||
|
Prefix=/var/mqm
|
||||||
|
InstallationName=Installation1
|
||||||
73
internal/mqini/mqini.go
Normal file
73
internal/mqini/mqini.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 mqini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ibm-messaging/mq-container/internal/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueueManager describe high-level configuration information for a queue manager
|
||||||
|
type QueueManager struct {
|
||||||
|
Name string
|
||||||
|
Prefix string
|
||||||
|
Directory string
|
||||||
|
DataPath string
|
||||||
|
InstallationName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getQueueManagerFromStanza parses a queue manager stanza
|
||||||
|
func getQueueManagerFromStanza(stanza string) (*QueueManager, error) {
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(stanza))
|
||||||
|
qm := QueueManager{}
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := scanner.Text()
|
||||||
|
l = strings.TrimSpace(l)
|
||||||
|
t := strings.Split(l, "=")
|
||||||
|
switch t[0] {
|
||||||
|
case "Name":
|
||||||
|
qm.Name = t[1]
|
||||||
|
case "Prefix":
|
||||||
|
qm.Prefix = t[1]
|
||||||
|
case "Directory":
|
||||||
|
qm.Directory = t[1]
|
||||||
|
case "DataPath":
|
||||||
|
qm.DataPath = t[1]
|
||||||
|
case "InstallationName":
|
||||||
|
qm.InstallationName = t[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &qm, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueueManager returns queue manager configuration information
|
||||||
|
func GetQueueManager(name string) (*QueueManager, error) {
|
||||||
|
// dspmqinf essentially returns a subset of mqs.ini, but it's simpler to parse
|
||||||
|
out, _, err := command.Run("dspmqinf", "-o", "stanza", name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return getQueueManagerFromStanza(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorLogDirectory returns the directory holding the error logs for the
|
||||||
|
// specified queue manager
|
||||||
|
func GetErrorLogDirectory(qm *QueueManager) string {
|
||||||
|
return filepath.Join(qm.Prefix, "qmgrs", qm.Directory, "errors")
|
||||||
|
}
|
||||||
64
internal/mqini/mqini_test.go
Normal file
64
internal/mqini/mqini_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 mqini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var getQueueManagerTests = []struct {
|
||||||
|
file string
|
||||||
|
name string
|
||||||
|
prefix string
|
||||||
|
directory string
|
||||||
|
errorLogDir string
|
||||||
|
}{
|
||||||
|
{"dspmqinf1.txt", "foo", "/var/mqm", "foo", "/var/mqm/qmgrs/foo/errors"},
|
||||||
|
{"dspmqinf2.txt", "a/b", "/var/mqm", "a&b", "/var/mqm/qmgrs/a&b/errors"},
|
||||||
|
{"dspmqinf3.txt", "..", "/var/mqm", "!!", "/var/mqm/qmgrs/!!/errors"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetQueueManager(t *testing.T) {
|
||||||
|
for _, table := range getQueueManagerTests {
|
||||||
|
t.Run(table.file, func(t *testing.T) {
|
||||||
|
b, err := ioutil.ReadFile(table.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
qm, err := getQueueManagerFromStanza(string(b))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("%#v", qm)
|
||||||
|
if qm.Name != table.name {
|
||||||
|
t.Errorf("Expected name=%v; got %v", table.name, qm.Name)
|
||||||
|
}
|
||||||
|
if qm.Prefix != table.prefix {
|
||||||
|
t.Errorf("Expected prefix=%v; got %v", table.prefix, qm.Prefix)
|
||||||
|
}
|
||||||
|
if qm.Directory != table.directory {
|
||||||
|
t.Errorf("Expected directory=%v; got %v", table.directory, qm.Directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test
|
||||||
|
d := GetErrorLogDirectory(qm)
|
||||||
|
if d != table.errorLogDir {
|
||||||
|
t.Errorf("Expected error log directory=%v; got %v", table.errorLogDir, d)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,11 +20,9 @@ package name
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
//log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// sanitizeQueueManagerName removes any invalid characters from a queue manager name
|
// sanitizeQueueManagerName removes any invalid characters from a queue manager name
|
||||||
// TODO: This is duplicate code
|
|
||||||
func sanitizeQueueManagerName(name string) string {
|
func sanitizeQueueManagerName(name string) string {
|
||||||
var re = regexp.MustCompile("[^a-zA-Z0-9._%/]")
|
var re = regexp.MustCompile("[^a-zA-Z0-9._%/]")
|
||||||
return re.ReplaceAllString(name, "")
|
return re.ReplaceAllString(name, "")
|
||||||
64
internal/ready/ready.go
Normal file
64
internal/ready/ready.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 ready contains code to provide a ready signal mechanism between processes
|
||||||
|
package ready
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileName string = "/run/runmqserver/ready"
|
||||||
|
|
||||||
|
func fileExists() (bool, error) {
|
||||||
|
_, err := os.Stat(fileName)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear ensures that any readiness state is cleared
|
||||||
|
func Clear() error {
|
||||||
|
exist, err := fileExists()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
return os.Remove(fileName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set lets any subsequent calls to `CheckReady` know that the queue
|
||||||
|
// manager has finished its configuration step
|
||||||
|
func Set() error {
|
||||||
|
return ioutil.WriteFile(fileName, []byte("1"), 0770)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check checks whether or not the queue manager has finished its
|
||||||
|
// configuration steps
|
||||||
|
func Check() (bool, error) {
|
||||||
|
exists, err := fileExists()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# © Copyright IBM Corporation 2017
|
# © Copyright IBM Corporation 2017, 2018
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,4 +17,8 @@
|
|||||||
version = "^1.12"
|
version = "^1.12"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/docker/go-connections"
|
name = "github.com/docker/go-connections"
|
||||||
|
version = "0.3.0"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
|||||||
72
test/docker/devconfig_test.go
Normal file
72
test/docker/devconfig_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// +build mqdev
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDevGoldenPath(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"},
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
waitForReady(t, cli, id)
|
||||||
|
waitForWebReady(t, cli, id)
|
||||||
|
|
||||||
|
timeout := time.Duration(30 * time.Second)
|
||||||
|
// Disable TLS verification (server uses a self-signed certificate by default,
|
||||||
|
// so verification isn't useful anyway)
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
httpClient := http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getWebPort(t, cli, id))
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
req.SetBasicAuth("admin", "passw0rd")
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the container cleanly
|
||||||
|
stopContainer(t, cli, id)
|
||||||
|
}
|
||||||
42
test/docker/devconfig_test_util.go
Normal file
42
test/docker/devconfig_test_util.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// +build mqdev
|
||||||
|
|
||||||
|
/*
|
||||||
|
© Copyright IBM Corporation 2018
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitForWebReady(t *testing.T, cli *client.Client, ID string) {
|
||||||
|
config := tls.Config{InsecureSkipVerify: true}
|
||||||
|
a := fmt.Sprintf("localhost:%s", getWebPort(t, cli, ID))
|
||||||
|
for {
|
||||||
|
conn, err := tls.Dial("tcp", a, &config)
|
||||||
|
if err == nil {
|
||||||
|
conn.Close()
|
||||||
|
// Extra sleep to allow web apps to start
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
t.Log("MQ web server is ready")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -16,9 +16,18 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
@@ -27,6 +36,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLicenseNotSet(t *testing.T) {
|
func TestLicenseNotSet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -38,9 +48,11 @@ func TestLicenseNotSet(t *testing.T) {
|
|||||||
if rc != 1 {
|
if rc != 1 {
|
||||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||||
}
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLicenseView(t *testing.T) {
|
func TestLicenseView(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -61,21 +73,44 @@ func TestLicenseView(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGoldenPath starts a queue manager successfully
|
||||||
func TestGoldenPath(t *testing.T) {
|
func TestGoldenPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||||
//ExposedPorts: ports,
|
|
||||||
ExposedPorts: nat.PortSet{
|
|
||||||
"1414/tcp": struct{}{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
id := runContainer(t, cli, &containerConfig)
|
id := runContainer(t, cli, &containerConfig)
|
||||||
defer cleanContainer(t, cli, id)
|
defer cleanContainer(t, cli, id)
|
||||||
waitForReady(t, cli, id)
|
waitForReady(t, cli, id)
|
||||||
|
// Stop the container cleanly
|
||||||
|
stopContainer(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)
|
||||||
|
}
|
||||||
|
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "test -d /etc/apt")
|
||||||
|
if rc != 0 {
|
||||||
|
t.Skip("Skipping test because container is not Ubuntu-based")
|
||||||
|
}
|
||||||
|
// Override the entrypoint to make "apt" only receive security updates, then check for updates
|
||||||
|
rc, log := runContainerOneShot(t, cli, "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")
|
||||||
|
if rc != 0 {
|
||||||
|
t.Fatalf("Expected success, got %v", rc)
|
||||||
|
}
|
||||||
|
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) {
|
func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) {
|
||||||
@@ -87,29 +122,29 @@ func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName stri
|
|||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Env: []string{"LICENSE=accept"},
|
Env: []string{"LICENSE=accept"},
|
||||||
Hostname: hostName,
|
Hostname: hostName,
|
||||||
ExposedPorts: nat.PortSet{
|
|
||||||
"1414/tcp": struct{}{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
id := runContainer(t, cli, &containerConfig)
|
id := runContainer(t, cli, &containerConfig)
|
||||||
defer cleanContainer(t, cli, id)
|
defer cleanContainer(t, cli, id)
|
||||||
waitForReady(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) {
|
if !strings.Contains(out, search) {
|
||||||
t.Errorf("Expected result of running dspmq to contain name=%v, got name=%v", search, out)
|
t.Errorf("Expected result of running dspmq to contain name=%v, got name=%v", search, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestNoQueueManagerName(t *testing.T) {
|
func TestNoQueueManagerName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
utilTestNoQueueManagerName(t, "test", "test")
|
utilTestNoQueueManagerName(t, "test", "test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
|
func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
utilTestNoQueueManagerName(t, "test-1", "test1")
|
utilTestNoQueueManagerName(t, "test-1", "test1")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithVolume runs a container with a Docker volume, then removes that
|
// TestWithVolume runs a container with a Docker volume, then removes that
|
||||||
// container and starts a new one with same volume.
|
// container and starts a new one with same volume.
|
||||||
func TestWithVolume(t *testing.T) {
|
func TestWithVolume(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -123,7 +158,6 @@ func TestWithVolume(t *testing.T) {
|
|||||||
hostConfig := container.HostConfig{
|
hostConfig := container.HostConfig{
|
||||||
Binds: []string{
|
Binds: []string{
|
||||||
coverageBind(t),
|
coverageBind(t),
|
||||||
//"coverage:/var/coverage",
|
|
||||||
vol.Name + ":/mnt/mqm",
|
vol.Name + ":/mnt/mqm",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -149,17 +183,16 @@ func TestWithVolume(t *testing.T) {
|
|||||||
waitForReady(t, cli, ctr2.ID)
|
waitForReady(t, cli, ctr2.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNoVolumeWithRestart ensures a queue manager container can be stopped
|
||||||
|
// and restarted cleanly
|
||||||
func TestNoVolumeWithRestart(t *testing.T) {
|
func TestNoVolumeWithRestart(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||||
//ExposedPorts: ports,
|
|
||||||
ExposedPorts: nat.PortSet{
|
|
||||||
"1414/tcp": struct{}{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
id := runContainer(t, cli, &containerConfig)
|
id := runContainer(t, cli, &containerConfig)
|
||||||
defer cleanContainer(t, cli, id)
|
defer cleanContainer(t, cli, id)
|
||||||
@@ -168,3 +201,381 @@ func TestNoVolumeWithRestart(t *testing.T) {
|
|||||||
startContainer(t, cli, id)
|
startContainer(t, cli, id)
|
||||||
waitForReady(t, cli, id)
|
waitForReady(t, cli, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateQueueManagerFail causes a failure of `crtmqm`
|
||||||
|
func TestCreateQueueManagerFail(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
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"},
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStartQueueManagerFail causes a failure of `strmqm`
|
||||||
|
func TestStartQueueManagerFail(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
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", "DEBUG=1"},
|
||||||
|
// Override the entrypoint to replace `strmqm` with a script which deletes the queue manager.
|
||||||
|
// This will cause `strmqm` to return with an exit code of 72.
|
||||||
|
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && 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)
|
||||||
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
out = execContainerWithOutput(t, cli, id, "mqm", []string{"bash", "-c", "ps -lA | grep '^. Z'"})
|
||||||
|
if out != "" {
|
||||||
|
count := strings.Count(out, "\n") + 1
|
||||||
|
t.Errorf("Expected zombies=0, got %v", count)
|
||||||
|
t.Error(out)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMQSC creates a new image with an MQSC file in, starts a container based
|
||||||
|
// on that image, and checks that the MQSC has been applied correctly.
|
||||||
|
func TestMQSC(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var files = []struct {
|
||||||
|
Name, Body string
|
||||||
|
}{
|
||||||
|
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())},
|
||||||
|
{"test.mqsc", "DEFINE QLOCAL(test)"},
|
||||||
|
}
|
||||||
|
tag := createImage(t, cli, files)
|
||||||
|
defer deleteImage(t, cli, tag)
|
||||||
|
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||||
|
Image: tag,
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
waitForReady(t, cli, id)
|
||||||
|
rc := execContainerWithExitCode(t, cli, id, "mqm", []string{"bash", "-c", "echo 'DISPLAY QLOCAL(test)' | runmqsc"})
|
||||||
|
if rc != 0 {
|
||||||
|
t.Fatalf("Expected runmqsc to exit with rc=0, got %v", rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadiness creates a new image with large amounts of MQSC in, to
|
||||||
|
// ensure that the readiness check doesn't pass until configuration has finished.
|
||||||
|
// WARNING: This test is sensitive to the speed of the machine it's running on.
|
||||||
|
func TestReadiness(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
const numQueues = 3
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 1; i <= numQueues; i++ {
|
||||||
|
fmt.Fprintf(&buf, "* Defining queue test %v\nDEFINE QLOCAL(test%v)\n", i, i)
|
||||||
|
}
|
||||||
|
var files = []struct {
|
||||||
|
Name, Body string
|
||||||
|
}{
|
||||||
|
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())},
|
||||||
|
{"test.mqsc", buf.String()},
|
||||||
|
}
|
||||||
|
tag := createImage(t, cli, files)
|
||||||
|
defer deleteImage(t, cli, tag)
|
||||||
|
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=1"},
|
||||||
|
Image: tag,
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
queueCheckCommand := fmt.Sprintf("echo 'DISPLAY QLOCAL(test%v)' | runmqsc", numQueues)
|
||||||
|
t.Log(execContainerWithOutput(t, cli, id, "root", []string{"cat", "/etc/mqm/test.mqsc"}))
|
||||||
|
for {
|
||||||
|
readyRC := execContainerWithExitCode(t, cli, id, "mqm", []string{"chkmqready"})
|
||||||
|
queueCheckRC := execContainerWithExitCode(t, cli, id, "mqm", []string{"bash", "-c", queueCheckCommand})
|
||||||
|
t.Logf("readyRC=%v,queueCheckRC=%v\n", readyRC, queueCheckRC)
|
||||||
|
if readyRC == 0 {
|
||||||
|
if queueCheckRC != 0 {
|
||||||
|
t.Fatalf("chkmqready returned %v when MQSC had not finished", readyRC)
|
||||||
|
} else {
|
||||||
|
// chkmqready says OK, and the last queue exists, so return
|
||||||
|
t.Log(execContainerWithOutput(t, cli, id, "root", []string{"bash", "-c", "echo 'DISPLAY QLOCAL(test1)' | runmqsc"}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func countLines(t *testing.T, r io.Reader) int {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
count := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
err := scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func countTarLines(t *testing.T, b []byte) int {
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
tr := tar.NewReader(r)
|
||||||
|
total := 0
|
||||||
|
for {
|
||||||
|
_, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
// End of TAR
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
total += countLines(t, tr)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorLogRotation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
qmName := "qm1"
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{
|
||||||
|
"LICENSE=accept",
|
||||||
|
"MQ_QMGR_NAME=" + qmName,
|
||||||
|
"MQMAXERRORLOGSIZE=65536",
|
||||||
|
"LOG_FORMAT=json",
|
||||||
|
},
|
||||||
|
ExposedPorts: nat.PortSet{
|
||||||
|
"1414/tcp": struct{}{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
waitForReady(t, cli, id)
|
||||||
|
dir := "/var/mqm/qmgrs/" + qmName + "/errors"
|
||||||
|
// Generate some content for the error logs, by trying to put messages under an unauthorized user
|
||||||
|
// execContainerWithOutput(t, cli, id, "fred", []string{"bash", "-c", "for i in {1..30} ; do /opt/mqm/samp/bin/amqsput FAKE; done"})
|
||||||
|
execContainerWithOutput(t, cli, id, "root", []string{"useradd", "fred"})
|
||||||
|
for {
|
||||||
|
execContainerWithOutput(t, cli, id, "fred", []string{"bash", "-c", "/opt/mqm/samp/bin/amqsput FAKE"})
|
||||||
|
amqerr02size, err := strconv.Atoi(execContainerWithOutput(t, cli, id, "mqm", []string{"bash", "-c", "wc -c < " + filepath.Join(dir, "AMQERR02.json")}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if amqerr02size > 0 {
|
||||||
|
// We've done enough to cause log rotation
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := execContainerWithOutput(t, cli, id, "root", []string{"ls", "-l", dir})
|
||||||
|
t.Log(out)
|
||||||
|
stopContainer(t, cli, id)
|
||||||
|
b := copyFromContainer(t, cli, id, filepath.Join(dir, "AMQERR01.json"))
|
||||||
|
amqerr01 := countTarLines(t, b)
|
||||||
|
b = copyFromContainer(t, cli, id, filepath.Join(dir, "AMQERR02.json"))
|
||||||
|
amqerr02 := countTarLines(t, b)
|
||||||
|
b = copyFromContainer(t, cli, id, filepath.Join(dir, "AMQERR03.json"))
|
||||||
|
amqerr03 := countTarLines(t, b)
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
|
||||||
|
totalMirrored := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
if strings.Contains(scanner.Text(), "\"message\":\"AMQ") {
|
||||||
|
totalMirrored++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
total := amqerr01 + amqerr02 + amqerr03
|
||||||
|
if totalMirrored != total {
|
||||||
|
t.Fatalf("Expected %v (%v + %v + %v) mirrored log entries; got %v", total, amqerr01, amqerr02, amqerr03, totalMirrored)
|
||||||
|
} else {
|
||||||
|
t.Logf("Found %v (%v + %v + %v) mirrored log entries", totalMirrored, amqerr01, amqerr02, amqerr03)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONLogFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{
|
||||||
|
"LICENSE=accept",
|
||||||
|
"LOG_FORMAT=json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
waitForReady(t, cli, id)
|
||||||
|
stopContainer(t, cli, id)
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
|
||||||
|
for scanner.Scan() {
|
||||||
|
var obj map[string]interface{}
|
||||||
|
s := scanner.Text()
|
||||||
|
err := json.Unmarshal([]byte(s), &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected all log lines to be valid JSON. Got error %v for line %v", err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadLogFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{
|
||||||
|
"LICENSE=accept",
|
||||||
|
"LOG_FORMAT=fake",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMQJSONDisabled tests the case where MQ's JSON logging feature is
|
||||||
|
// specifically disabled (which will disable log mirroring)
|
||||||
|
func TestMQJSONDisabled(t *testing.T) {
|
||||||
|
t.SkipNow()
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{
|
||||||
|
"LICENSE=accept",
|
||||||
|
"MQ_QMGR_NAME=qm1",
|
||||||
|
"AMQ_ADDITIONAL_JSON_LOG=0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
waitForReady(t, cli, id)
|
||||||
|
// Stop the container (which could hang if runmqserver is still waiting for
|
||||||
|
// JSON logs to appear)
|
||||||
|
stopContainer(t, cli, id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© Copyright IBM Corporation 2017
|
© Copyright IBM Corporation 2017, 2018
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -16,12 +16,19 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,6 +37,8 @@ import (
|
|||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,12 +50,64 @@ func imageName() string {
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
func coverageBind(t *testing.T) string {
|
func coverage() bool {
|
||||||
|
cover := os.Getenv("TEST_COVER")
|
||||||
|
if cover == "true" || cover == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverageDir returns the host directory to use for code coverage data
|
||||||
|
func coverageDir(t *testing.T) string {
|
||||||
dir, err := os.Getwd()
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return filepath.Join(dir, "coverage") + ":/var/coverage"
|
return filepath.Join(dir, "coverage")
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverageBind returns a string to use to add a bind-mounted directory for code coverage data
|
||||||
|
func coverageBind(t *testing.T) string {
|
||||||
|
return coverageDir(t) + ":/var/coverage"
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminationLog returns the name of the file to use for the termination log message
|
||||||
|
func terminationLog(t *testing.T) string {
|
||||||
|
// Warning: this directory must be accessible to the Docker daemon,
|
||||||
|
// in order to enable the bind mount
|
||||||
|
return "/tmp/" + t.Name() + "-termination-log"
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminationBind returns a string to use to bind-mount a termination log file.
|
||||||
|
// This is done using a bind, because you can't copy files from /dev out of the container.
|
||||||
|
func terminationBind(t *testing.T) string {
|
||||||
|
n := terminationLog(t)
|
||||||
|
// Remove it if it already exists
|
||||||
|
os.Remove(n)
|
||||||
|
// Create the empty file
|
||||||
|
f, err := os.OpenFile(n, os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
return n + ":/dev/termination-log"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the termination message, or an empty string if not set
|
||||||
|
func terminationMessage(t *testing.T) string {
|
||||||
|
b, err := ioutil.ReadFile(terminationLog(t))
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectTerminationMessage(t *testing.T) {
|
||||||
|
m := terminationMessage(t)
|
||||||
|
if m == "" {
|
||||||
|
t.Error("Expected termination message to be set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
||||||
@@ -54,20 +115,32 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// Log the results and continue
|
// Log the results and continue
|
||||||
t.Logf("Inspected container %v: %#v", ID, i)
|
t.Logf("Inspected container %v: %#v", ID, i)
|
||||||
|
s, err := json.MarshalIndent(i, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("Inspected container %v: %v", ID, string(s))
|
||||||
}
|
}
|
||||||
t.Logf("Killing container: %v", ID)
|
t.Logf("Stopping container: %v", ID)
|
||||||
// Kill the container. This allows the coverage output to be generated.
|
timeout := 10 * time.Second
|
||||||
err = cli.ContainerKill(context.Background(), ID, "SIGTERM")
|
// Stop the container. This allows the coverage output to be generated.
|
||||||
|
err = cli.ContainerStop(context.Background(), ID, &timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Just log the error and continue
|
// Just log the error and continue
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
}
|
}
|
||||||
//waitForContainer(t, cli, ID, 20, container.WaitConditionNotRunning)
|
t.Log("Container stopped")
|
||||||
|
|
||||||
// TODO: This is probably no longer necessary
|
// If a code coverage file has been generated, then rename it to match the test name
|
||||||
time.Sleep(20 * time.Second)
|
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
|
// 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("Console log from container %v:\n%v", ID, inspectTextLogs(t, cli, ID))
|
||||||
|
|
||||||
|
m := terminationMessage(t)
|
||||||
|
if m != "" {
|
||||||
|
t.Logf("Termination message: %v", m)
|
||||||
|
}
|
||||||
|
os.Remove(terminationLog(t))
|
||||||
|
|
||||||
t.Logf("Removing container: %s", ID)
|
t.Logf("Removing container: %s", ID)
|
||||||
opts := types.ContainerRemoveOptions{
|
opts := types.ContainerRemoveOptions{
|
||||||
@@ -87,12 +160,19 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
|
|||||||
if containerConfig.Image == "" {
|
if containerConfig.Image == "" {
|
||||||
containerConfig.Image = imageName()
|
containerConfig.Image = imageName()
|
||||||
}
|
}
|
||||||
|
// if coverage
|
||||||
|
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
|
||||||
hostConfig := container.HostConfig{
|
hostConfig := container.HostConfig{
|
||||||
|
Binds: []string{
|
||||||
|
coverageBind(t),
|
||||||
|
terminationBind(t),
|
||||||
|
},
|
||||||
|
// Assign a random port for the web server on the host
|
||||||
|
// TODO: Don't do this for all tests
|
||||||
PortBindings: nat.PortMap{
|
PortBindings: nat.PortMap{
|
||||||
"1414/tcp": []nat.PortBinding{
|
"9443/tcp": []nat.PortBinding{
|
||||||
{
|
{
|
||||||
HostIP: "0.0.0.0",
|
HostIP: "0.0.0.0",
|
||||||
HostPort: "1414",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -107,6 +187,15 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
|
|||||||
return ctr.ID
|
return ctr.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runContainerOneShot(t *testing.T, cli *client.Client, command ...string) (int64, string) {
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Entrypoint: command,
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
return waitForContainer(t, cli, id, 10), inspectLogs(t, cli, id)
|
||||||
|
}
|
||||||
|
|
||||||
func startContainer(t *testing.T, cli *client.Client, ID string) {
|
func startContainer(t *testing.T, cli *client.Client, ID string) {
|
||||||
t.Logf("Starting container: %v", ID)
|
t.Logf("Starting container: %v", ID)
|
||||||
startOptions := types.ContainerStartOptions{}
|
startOptions := types.ContainerStartOptions{}
|
||||||
@@ -125,24 +214,85 @@ 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
|
// waitForContainer waits until a container has exited
|
||||||
func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout int64) int64 {
|
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)
|
rc, err := cli.ContainerWait(context.Background(), ID)
|
||||||
// err := <-errC
|
|
||||||
|
if coverage() {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// wait := <-waitC
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// execContainer runs the specified command inside the container, returning the
|
// execContainerWithExitCode runs a command in a running container, and returns the exit code
|
||||||
// exit code and the stdout/stderr string.
|
// Note: due to a bug in Docker/Moby code, you always get an exit code of 0 if you attach to the
|
||||||
func execContainer(t *testing.T, cli *client.Client, ID string, cmd []string) (int, string) {
|
// 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{
|
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,
|
Privileged: false,
|
||||||
Tty: false,
|
Tty: false,
|
||||||
AttachStdin: false,
|
AttachStdin: false,
|
||||||
@@ -159,53 +309,36 @@ func execContainer(t *testing.T, cli *client.Client, ID string, cmd []string) (i
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
cli.ContainerExecStart(context.Background(), resp.ID, types.ExecStartCheck{
|
err = cli.ContainerExecStart(context.Background(), resp.ID, types.ExecStartCheck{
|
||||||
Detach: false,
|
Detach: false,
|
||||||
Tty: false,
|
Tty: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID)
|
// Wait for the command to finish
|
||||||
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 {
|
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)
|
inspect, err := cli.ContainerExecInspect(context.Background(), resp.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if inspect.ExitCode == 0 {
|
if !inspect.Running {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForReady(t *testing.T, cli *client.Client, ID string) {
|
||||||
|
for {
|
||||||
|
rc := execContainerWithExitCode(t, cli, ID, "mqm", []string{"chkmqready"})
|
||||||
|
if rc == 0 {
|
||||||
t.Log("MQ is ready")
|
t.Log("MQ is ready")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,6 +395,29 @@ func removeVolume(t *testing.T, cli *client.Client, name string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inspectTextLogs(t *testing.T, cli *client.Client, ID string) string {
|
||||||
|
jsonLogs := inspectLogs(t, cli, ID)
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(jsonLogs))
|
||||||
|
b := make([]byte, 64*1024)
|
||||||
|
scanner.Buffer(b, 1024*1024)
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
for scanner.Scan() {
|
||||||
|
t := scanner.Text()
|
||||||
|
if strings.HasPrefix(t, "{") {
|
||||||
|
var e map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(scanner.Text()), &e)
|
||||||
|
fmt.Fprintf(buf, "{\"message\": \"%v\"}\n", e["message"])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(buf, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func inspectLogs(t *testing.T, cli *client.Client, ID string) string {
|
func inspectLogs(t *testing.T, cli *client.Client, ID string) string {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -272,8 +428,96 @@ func inspectLogs(t *testing.T, cli *client.Client, ID string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
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()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateTAR creates a TAR-formatted []byte, with the specified files included.
|
||||||
|
func generateTAR(t *testing.T, files []struct{ Name, Body string }) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tw := tar.NewWriter(buf)
|
||||||
|
for _, file := range files {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: file.Name,
|
||||||
|
Mode: 0600,
|
||||||
|
Size: int64(len(file.Body)),
|
||||||
|
}
|
||||||
|
err := tw.WriteHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = tw.Write([]byte(file.Body))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := tw.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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{
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWebPort(t *testing.T, cli *client.Client, ID string) string {
|
||||||
|
i, err := cli.ContainerInspect(context.Background(), ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return i.NetworkSettings.Ports["9443/tcp"][0].HostPort
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# © Copyright IBM Corporation 2017
|
# © Copyright IBM Corporation 2017, 2018
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -27,3 +27,7 @@
|
|||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "k8s.io/apimachinery"
|
name = "k8s.io/apimachinery"
|
||||||
branch = "release-1.7"
|
branch = "release-1.7"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
unused-packages = true
|
||||||
|
go-tests = true
|
||||||
|
|||||||
@@ -17,36 +17,34 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"github.com/ibm-messaging/mq-container/internal/command"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func imageName() string {
|
func image(t *testing.T) string {
|
||||||
v, ok := os.LookupEnv("TEST_REPO")
|
v, ok := os.LookupEnv("TEST_IMAGE")
|
||||||
if !ok {
|
if !ok {
|
||||||
v = "ibmcom/mq"
|
t.Fatal("TEST_IMAGE environment variable not set")
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageTag() string {
|
func imageName(t *testing.T) string {
|
||||||
v, ok := os.LookupEnv("TEST_TAG")
|
return strings.Fields(strings.Replace(image(t), ":", " ", -1))[0]
|
||||||
if !ok {
|
}
|
||||||
v = "latest"
|
|
||||||
}
|
func imageTag(t *testing.T) string {
|
||||||
return v
|
return strings.Fields(strings.Replace(image(t), ":", " ", -1))[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func chartName() string {
|
func chartName() string {
|
||||||
@@ -57,32 +55,6 @@ func chartName() string {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// runCommand runs an OS command. On Linux it waits for the command to
|
|
||||||
// complete and returns the exit status (return code).
|
|
||||||
// TODO: duplicated from cmd/runmqserver/main.go
|
|
||||||
func runCommand(t *testing.T, name string, arg ...string) (string, int, error) {
|
|
||||||
t.Logf("Running command: %v %v", name, strings.Trim(fmt.Sprintf("%v", arg), "[]"))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func inspectLogs(t *testing.T, cs *kubernetes.Clientset, release string) string {
|
func inspectLogs(t *testing.T, cs *kubernetes.Clientset, release string) string {
|
||||||
pods := getPodsForHelmRelease(t, cs, release)
|
pods := getPodsForHelmRelease(t, cs, release)
|
||||||
opt := v1.PodLogOptions{}
|
opt := v1.PodLogOptions{}
|
||||||
@@ -98,7 +70,7 @@ func inspectLogs(t *testing.T, cs *kubernetes.Clientset, release string) string
|
|||||||
|
|
||||||
func helmInstall(t *testing.T, cs *kubernetes.Clientset, release string, values ...string) {
|
func helmInstall(t *testing.T, cs *kubernetes.Clientset, release string, values ...string) {
|
||||||
chart := chartName()
|
chart := chartName()
|
||||||
tag := "latest"
|
tag := imageTag(t)
|
||||||
arg := []string{
|
arg := []string{
|
||||||
"install",
|
"install",
|
||||||
"--debug",
|
"--debug",
|
||||||
@@ -106,7 +78,7 @@ func helmInstall(t *testing.T, cs *kubernetes.Clientset, release string, values
|
|||||||
"--name",
|
"--name",
|
||||||
release,
|
release,
|
||||||
"--set",
|
"--set",
|
||||||
"image.repository=" + imageName(),
|
"image.repository=" + imageName(t),
|
||||||
"--set",
|
"--set",
|
||||||
"image.tag=" + tag,
|
"image.tag=" + tag,
|
||||||
"--set",
|
"--set",
|
||||||
@@ -116,7 +88,7 @@ func helmInstall(t *testing.T, cs *kubernetes.Clientset, release string, values
|
|||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
arg = append(arg, "--set", value)
|
arg = append(arg, "--set", value)
|
||||||
}
|
}
|
||||||
out, _, err := runCommand(t, "helm", arg...)
|
out, _, err := command.Run("helm", arg...)
|
||||||
t.Log(out)
|
t.Log(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(out)
|
t.Error(out)
|
||||||
@@ -127,7 +99,7 @@ func helmInstall(t *testing.T, cs *kubernetes.Clientset, release string, values
|
|||||||
func helmDelete(t *testing.T, cs *kubernetes.Clientset, release string) {
|
func helmDelete(t *testing.T, cs *kubernetes.Clientset, release string) {
|
||||||
t.Log("Deleting Helm release")
|
t.Log("Deleting Helm release")
|
||||||
t.Log(inspectLogs(t, cs, release))
|
t.Log(inspectLogs(t, cs, release))
|
||||||
out, _, err := runCommand(t, "helm", "delete", "--purge", release)
|
out, _, err := command.Run("helm", "delete", "--purge", release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(out)
|
t.Error(out)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -151,7 +123,10 @@ func helmDeletePVC(t *testing.T, cs *kubernetes.Clientset, release string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func kubeLogin(t *testing.T) *kubernetes.Clientset {
|
func kubeLogin(t *testing.T) *kubernetes.Clientset {
|
||||||
kc := os.Getenv("HOME") + "/.kube/config"
|
kc := os.Getenv("KUBECONFIG")
|
||||||
|
if kc == "" {
|
||||||
|
kc = os.Getenv("HOME") + "/.kube/config"
|
||||||
|
}
|
||||||
c, err := clientcmd.BuildConfigFromFlags("", kc)
|
c, err := clientcmd.BuildConfigFromFlags("", kc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -167,7 +142,7 @@ func kubeExec(t *testing.T, podName string, name string, arg ...string) (string,
|
|||||||
// Current version of Kubernetes Go client doesn't support "exec", so run this via the command line
|
// Current version of Kubernetes Go client doesn't support "exec", so run this via the command line
|
||||||
param := []string{"exec", podName, "--", name}
|
param := []string{"exec", podName, "--", name}
|
||||||
param = append(param, arg...)
|
param = append(param, arg...)
|
||||||
return runCommand(t, "kubectl", param...)
|
return command.Run("kubectl", param...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForReady(t *testing.T, cs *kubernetes.Clientset, release string) {
|
func waitForReady(t *testing.T, cs *kubernetes.Clientset, release string) {
|
||||||
@@ -199,12 +174,12 @@ func waitForReady(t *testing.T, cs *kubernetes.Clientset, release string) {
|
|||||||
for {
|
for {
|
||||||
// Current version of Kubernetes Go client doesn't support "exec", so run this via the command line
|
// Current version of Kubernetes Go client doesn't support "exec", so run this via the command line
|
||||||
// TODO: If we run "chkmqready" here, it doesn't seem to work
|
// TODO: If we run "chkmqready" here, it doesn't seem to work
|
||||||
//out, _, err := runCommand(t, "kubectl", "exec", podName, "--", "dspmq")
|
//out, _, err := command.Run(t, "kubectl", "exec", podName, "--", "dspmq")
|
||||||
out, _, err := kubeExec(t, podName, "dspmq")
|
out, _, err := kubeExec(t, podName, "dspmq")
|
||||||
//out, rc, err := runCommand(t, "kubectl", "exec", podName, "--", "chkmqready")
|
//out, rc, err := command.Run(t, "kubectl", "exec", podName, "--", "chkmqready")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(out)
|
t.Error(out)
|
||||||
out2, _, err2 := runCommand(t, "kubectl", "describe", "pod", podName)
|
out2, _, err2 := command.Run("kubectl", "describe", "pod", podName)
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
t.Log(out2)
|
t.Log(out2)
|
||||||
}
|
}
|
||||||
@@ -258,17 +233,32 @@ func volumesAvailable(t *testing.T, cs *kubernetes.Clientset) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// digitsOnly returns only the digits from the supplied string
|
||||||
|
func digitsOnly(s string) string {
|
||||||
|
return strings.Map(
|
||||||
|
func(r rune) rune {
|
||||||
|
if unicode.IsDigit(r) {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
},
|
||||||
|
s,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// assertKubeVersion is used to assert that a test requires a specific version of Kubernetes
|
// assertKubeVersion is used to assert that a test requires a specific version of Kubernetes
|
||||||
func assertKubeVersion(t *testing.T, cs *kubernetes.Clientset, major int, minor int) {
|
func assertKubeVersion(t *testing.T, cs *kubernetes.Clientset, major int, minor int) {
|
||||||
v, err := cs.Discovery().ServerVersion()
|
v, err := cs.Discovery().ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
maj, err := strconv.Atoi(v.Major)
|
// Make sure we use only the digits from the version, to account for things like "1.8+"
|
||||||
|
maj, err := strconv.Atoi(digitsOnly(v.Major))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
min, err := strconv.Atoi(v.Minor)
|
// Make sure we use only the digits from the version, to account for things like "1.8+"
|
||||||
|
min, err := strconv.Atoi(digitsOnly(v.Minor))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
3
vendor/github.com/hpcloud/tail/.gitignore
generated
vendored
3
vendor/github.com/hpcloud/tail/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
.test
|
|
||||||
.go
|
|
||||||
|
|
||||||
18
vendor/github.com/hpcloud/tail/.travis.yml
generated
vendored
18
vendor/github.com/hpcloud/tail/.travis.yml
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -race -v ./...
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get gopkg.in/fsnotify.v1
|
|
||||||
- go get gopkg.in/tomb.v1
|
|
||||||
63
vendor/github.com/hpcloud/tail/CHANGES.md
generated
vendored
63
vendor/github.com/hpcloud/tail/CHANGES.md
generated
vendored
@@ -1,63 +0,0 @@
|
|||||||
# API v1 (gopkg.in/hpcloud/tail.v1)
|
|
||||||
|
|
||||||
## April, 2016
|
|
||||||
|
|
||||||
* Migrated to godep, as depman is not longer supported
|
|
||||||
* Introduced golang vendoring feature
|
|
||||||
* Fixed issue [#57](https://github.com/hpcloud/tail/issues/57) related to reopen deleted file
|
|
||||||
|
|
||||||
## July, 2015
|
|
||||||
|
|
||||||
* Fix inotify watcher leak; remove `Cleanup` (#51)
|
|
||||||
|
|
||||||
# API v0 (gopkg.in/hpcloud/tail.v0)
|
|
||||||
|
|
||||||
## June, 2015
|
|
||||||
|
|
||||||
* Don't return partial lines (PR #40)
|
|
||||||
* Use stable version of fsnotify (#46)
|
|
||||||
|
|
||||||
## July, 2014
|
|
||||||
|
|
||||||
* Fix tail for Windows (PR #36)
|
|
||||||
|
|
||||||
## May, 2014
|
|
||||||
|
|
||||||
* Improved rate limiting using leaky bucket (PR #29)
|
|
||||||
* Fix odd line splitting (PR #30)
|
|
||||||
|
|
||||||
## Apr, 2014
|
|
||||||
|
|
||||||
* LimitRate now discards read buffer (PR #28)
|
|
||||||
* allow reading of longer lines if MaxLineSize is unset (PR #24)
|
|
||||||
* updated deps.json to latest fsnotify (441bbc86b1)
|
|
||||||
|
|
||||||
## Feb, 2014
|
|
||||||
|
|
||||||
* added `Config.Logger` to suppress library logging
|
|
||||||
|
|
||||||
## Nov, 2013
|
|
||||||
|
|
||||||
* add Cleanup to remove leaky inotify watches (PR #20)
|
|
||||||
|
|
||||||
## Aug, 2013
|
|
||||||
|
|
||||||
* redesigned Location field (PR #12)
|
|
||||||
* add tail.Tell (PR #14)
|
|
||||||
|
|
||||||
## July, 2013
|
|
||||||
|
|
||||||
* Rate limiting (PR #10)
|
|
||||||
|
|
||||||
## May, 2013
|
|
||||||
|
|
||||||
* Detect file deletions/renames in polling file watcher (PR #1)
|
|
||||||
* Detect file truncation
|
|
||||||
* Fix potential race condition when reopening the file (issue 5)
|
|
||||||
* Fix potential blocking of `tail.Stop` (issue 4)
|
|
||||||
* Fix uncleaned up ChangeEvents goroutines after calling tail.Stop
|
|
||||||
* Support Follow=false
|
|
||||||
|
|
||||||
## Feb, 2013
|
|
||||||
|
|
||||||
* Initial open source release
|
|
||||||
19
vendor/github.com/hpcloud/tail/Dockerfile
generated
vendored
19
vendor/github.com/hpcloud/tail/Dockerfile
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
FROM golang
|
|
||||||
|
|
||||||
RUN mkdir -p $GOPATH/src/github.com/hpcloud/tail/
|
|
||||||
ADD . $GOPATH/src/github.com/hpcloud/tail/
|
|
||||||
|
|
||||||
# expecting to fetch dependencies successfully.
|
|
||||||
RUN go get -v github.com/hpcloud/tail
|
|
||||||
|
|
||||||
# expecting to run the test successfully.
|
|
||||||
RUN go test -v github.com/hpcloud/tail
|
|
||||||
|
|
||||||
# expecting to install successfully
|
|
||||||
RUN go install -v github.com/hpcloud/tail
|
|
||||||
RUN go install -v github.com/hpcloud/tail/cmd/gotail
|
|
||||||
|
|
||||||
RUN $GOPATH/bin/gotail -h || true
|
|
||||||
|
|
||||||
ENV PATH $GOPATH/bin:$PATH
|
|
||||||
CMD ["gotail"]
|
|
||||||
15
vendor/github.com/hpcloud/tail/Godeps/Godeps.json
generated
vendored
15
vendor/github.com/hpcloud/tail/Godeps/Godeps.json
generated
vendored
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"ImportPath": "github.com/hpcloud/tail",
|
|
||||||
"GoVersion": "go1.5.1",
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/fsnotify.v1",
|
|
||||||
"Comment": "v1.2.1",
|
|
||||||
"Rev": "7be54206639f256967dd82fa767397ba5f8f48f5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/tomb.v1",
|
|
||||||
"Rev": "c131134a1947e9afd9cecfe11f4c6dff0732ae58"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
5
vendor/github.com/hpcloud/tail/Godeps/Readme
generated
vendored
5
vendor/github.com/hpcloud/tail/Godeps/Readme
generated
vendored
@@ -1,5 +0,0 @@
|
|||||||
This directory tree is generated automatically by godep.
|
|
||||||
|
|
||||||
Please do not edit.
|
|
||||||
|
|
||||||
See https://github.com/tools/godep for more information.
|
|
||||||
21
vendor/github.com/hpcloud/tail/LICENSE.txt
generated
vendored
21
vendor/github.com/hpcloud/tail/LICENSE.txt
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
# The MIT License (MIT)
|
|
||||||
|
|
||||||
# © Copyright 2015 Hewlett Packard Enterprise Development LP
|
|
||||||
Copyright (c) 2014 ActiveState
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
11
vendor/github.com/hpcloud/tail/Makefile
generated
vendored
11
vendor/github.com/hpcloud/tail/Makefile
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
default: test
|
|
||||||
|
|
||||||
test: *.go
|
|
||||||
go test -v -race ./...
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
gofmt -w .
|
|
||||||
|
|
||||||
# Run the test in an isolated environment.
|
|
||||||
fulltest:
|
|
||||||
docker build -t hpcloud/tail .
|
|
||||||
28
vendor/github.com/hpcloud/tail/README.md
generated
vendored
28
vendor/github.com/hpcloud/tail/README.md
generated
vendored
@@ -1,28 +0,0 @@
|
|||||||
[](https://travis-ci.org/hpcloud/tail)
|
|
||||||
[](https://ci.appveyor.com/project/HelionCloudFoundry/tail)
|
|
||||||
|
|
||||||
# Go package for tail-ing files
|
|
||||||
|
|
||||||
A Go package striving to emulate the features of the BSD `tail` program.
|
|
||||||
|
|
||||||
```Go
|
|
||||||
t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
|
|
||||||
for line := range t.Lines {
|
|
||||||
fmt.Println(line.Text)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [API documentation](http://godoc.org/github.com/hpcloud/tail).
|
|
||||||
|
|
||||||
## Log rotation
|
|
||||||
|
|
||||||
Tail comes with full support for truncation/move detection as it is
|
|
||||||
designed to work with log rotation tools.
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
|
|
||||||
go get github.com/hpcloud/tail/...
|
|
||||||
|
|
||||||
## Windows support
|
|
||||||
|
|
||||||
This package [needs assistance](https://github.com/hpcloud/tail/labels/Windows) for full Windows support.
|
|
||||||
11
vendor/github.com/hpcloud/tail/appveyor.yml
generated
vendored
11
vendor/github.com/hpcloud/tail/appveyor.yml
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
version: 0.{build}
|
|
||||||
skip_tags: true
|
|
||||||
cache: C:\Users\appveyor\AppData\Local\NuGet\Cache
|
|
||||||
build_script:
|
|
||||||
- SET GOPATH=c:\workspace
|
|
||||||
- go test -v -race ./...
|
|
||||||
test: off
|
|
||||||
clone_folder: c:\workspace\src\github.com\hpcloud\tail
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
1
vendor/github.com/hpcloud/tail/cmd/gotail/.gitignore
generated
vendored
1
vendor/github.com/hpcloud/tail/cmd/gotail/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
|||||||
gotail
|
|
||||||
4
vendor/github.com/hpcloud/tail/cmd/gotail/Makefile
generated
vendored
4
vendor/github.com/hpcloud/tail/cmd/gotail/Makefile
generated
vendored
@@ -1,4 +0,0 @@
|
|||||||
default: gotail
|
|
||||||
|
|
||||||
gotail: *.go ../../*.go
|
|
||||||
go build
|
|
||||||
66
vendor/github.com/hpcloud/tail/cmd/gotail/gotail.go
generated
vendored
66
vendor/github.com/hpcloud/tail/cmd/gotail/gotail.go
generated
vendored
@@ -1,66 +0,0 @@
|
|||||||
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
|
|
||||||
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hpcloud/tail"
|
|
||||||
)
|
|
||||||
|
|
||||||
func args2config() (tail.Config, int64) {
|
|
||||||
config := tail.Config{Follow: true}
|
|
||||||
n := int64(0)
|
|
||||||
maxlinesize := int(0)
|
|
||||||
flag.Int64Var(&n, "n", 0, "tail from the last Nth location")
|
|
||||||
flag.IntVar(&maxlinesize, "max", 0, "max line size")
|
|
||||||
flag.BoolVar(&config.Follow, "f", false, "wait for additional data to be appended to the file")
|
|
||||||
flag.BoolVar(&config.ReOpen, "F", false, "follow, and track file rename/rotation")
|
|
||||||
flag.BoolVar(&config.Poll, "p", false, "use polling, instead of inotify")
|
|
||||||
flag.Parse()
|
|
||||||
if config.ReOpen {
|
|
||||||
config.Follow = true
|
|
||||||
}
|
|
||||||
config.MaxLineSize = maxlinesize
|
|
||||||
return config, n
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config, n := args2config()
|
|
||||||
if flag.NFlag() < 1 {
|
|
||||||
fmt.Println("need one or more files as arguments")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n != 0 {
|
|
||||||
config.Location = &tail.SeekInfo{-n, os.SEEK_END}
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan bool)
|
|
||||||
for _, filename := range flag.Args() {
|
|
||||||
go tailFile(filename, config, done)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, _ = range flag.Args() {
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailFile(filename string, config tail.Config, done chan bool) {
|
|
||||||
defer func() { done <- true }()
|
|
||||||
t, err := tail.TailFile(filename, config)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for line := range t.Lines {
|
|
||||||
fmt.Println(line.Text)
|
|
||||||
}
|
|
||||||
err = t.Wait()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
vendor/github.com/hpcloud/tail/ratelimiter/Licence
generated
vendored
7
vendor/github.com/hpcloud/tail/ratelimiter/Licence
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
Copyright (C) 2013 99designs
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
97
vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
generated
vendored
97
vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
generated
vendored
@@ -1,97 +0,0 @@
|
|||||||
// Package ratelimiter implements the Leaky Bucket ratelimiting algorithm with memcached and in-memory backends.
|
|
||||||
package ratelimiter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LeakyBucket struct {
|
|
||||||
Size uint16
|
|
||||||
Fill float64
|
|
||||||
LeakInterval time.Duration // time.Duration for 1 unit of size to leak
|
|
||||||
Lastupdate time.Time
|
|
||||||
Now func() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLeakyBucket(size uint16, leakInterval time.Duration) *LeakyBucket {
|
|
||||||
bucket := LeakyBucket{
|
|
||||||
Size: size,
|
|
||||||
Fill: 0,
|
|
||||||
LeakInterval: leakInterval,
|
|
||||||
Now: time.Now,
|
|
||||||
Lastupdate: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LeakyBucket) updateFill() {
|
|
||||||
now := b.Now()
|
|
||||||
if b.Fill > 0 {
|
|
||||||
elapsed := now.Sub(b.Lastupdate)
|
|
||||||
|
|
||||||
b.Fill -= float64(elapsed) / float64(b.LeakInterval)
|
|
||||||
if b.Fill < 0 {
|
|
||||||
b.Fill = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.Lastupdate = now
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LeakyBucket) Pour(amount uint16) bool {
|
|
||||||
b.updateFill()
|
|
||||||
|
|
||||||
var newfill float64 = b.Fill + float64(amount)
|
|
||||||
|
|
||||||
if newfill > float64(b.Size) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Fill = newfill
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The time at which this bucket will be completely drained
|
|
||||||
func (b *LeakyBucket) DrainedAt() time.Time {
|
|
||||||
return b.Lastupdate.Add(time.Duration(b.Fill * float64(b.LeakInterval)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// The duration until this bucket is completely drained
|
|
||||||
func (b *LeakyBucket) TimeToDrain() time.Duration {
|
|
||||||
return b.DrainedAt().Sub(b.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LeakyBucket) TimeSinceLastUpdate() time.Duration {
|
|
||||||
return b.Now().Sub(b.Lastupdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LeakyBucketSer struct {
|
|
||||||
Size uint16
|
|
||||||
Fill float64
|
|
||||||
LeakInterval time.Duration // time.Duration for 1 unit of size to leak
|
|
||||||
Lastupdate time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LeakyBucket) Serialise() *LeakyBucketSer {
|
|
||||||
bucket := LeakyBucketSer{
|
|
||||||
Size: b.Size,
|
|
||||||
Fill: b.Fill,
|
|
||||||
LeakInterval: b.LeakInterval,
|
|
||||||
Lastupdate: b.Lastupdate,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LeakyBucketSer) DeSerialise() *LeakyBucket {
|
|
||||||
bucket := LeakyBucket{
|
|
||||||
Size: b.Size,
|
|
||||||
Fill: b.Fill,
|
|
||||||
LeakInterval: b.LeakInterval,
|
|
||||||
Lastupdate: b.Lastupdate,
|
|
||||||
Now: time.Now,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bucket
|
|
||||||
}
|
|
||||||
73
vendor/github.com/hpcloud/tail/ratelimiter/leakybucket_test.go
generated
vendored
73
vendor/github.com/hpcloud/tail/ratelimiter/leakybucket_test.go
generated
vendored
@@ -1,73 +0,0 @@
|
|||||||
package ratelimiter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPour(t *testing.T) {
|
|
||||||
bucket := NewLeakyBucket(60, time.Second)
|
|
||||||
bucket.Lastupdate = time.Unix(0, 0)
|
|
||||||
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(1, 0) }
|
|
||||||
|
|
||||||
if bucket.Pour(61) {
|
|
||||||
t.Error("Expected false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bucket.Pour(10) {
|
|
||||||
t.Error("Expected true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bucket.Pour(49) {
|
|
||||||
t.Error("Expected true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket.Pour(2) {
|
|
||||||
t.Error("Expected false")
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(61, 0) }
|
|
||||||
if !bucket.Pour(60) {
|
|
||||||
t.Error("Expected true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket.Pour(1) {
|
|
||||||
t.Error("Expected false")
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(70, 0) }
|
|
||||||
|
|
||||||
if !bucket.Pour(1) {
|
|
||||||
t.Error("Expected true")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeSinceLastUpdate(t *testing.T) {
|
|
||||||
bucket := NewLeakyBucket(60, time.Second)
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(1, 0) }
|
|
||||||
bucket.Pour(1)
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(2, 0) }
|
|
||||||
|
|
||||||
sinceLast := bucket.TimeSinceLastUpdate()
|
|
||||||
if sinceLast != time.Second*1 {
|
|
||||||
t.Errorf("Expected time since last update to be less than 1 second, got %d", sinceLast)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeToDrain(t *testing.T) {
|
|
||||||
bucket := NewLeakyBucket(60, time.Second)
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(1, 0) }
|
|
||||||
bucket.Pour(10)
|
|
||||||
|
|
||||||
if bucket.TimeToDrain() != time.Second*10 {
|
|
||||||
t.Error("Time to drain should be 10 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket.Now = func() time.Time { return time.Unix(2, 0) }
|
|
||||||
|
|
||||||
if bucket.TimeToDrain() != time.Second*9 {
|
|
||||||
t.Error("Time to drain should be 9 seconds")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
vendor/github.com/hpcloud/tail/ratelimiter/memory.go
generated
vendored
58
vendor/github.com/hpcloud/tail/ratelimiter/memory.go
generated
vendored
@@ -1,58 +0,0 @@
|
|||||||
package ratelimiter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const GC_SIZE int = 100
|
|
||||||
|
|
||||||
type Memory struct {
|
|
||||||
store map[string]LeakyBucket
|
|
||||||
lastGCCollected time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMemory() *Memory {
|
|
||||||
m := new(Memory)
|
|
||||||
m.store = make(map[string]LeakyBucket)
|
|
||||||
m.lastGCCollected = time.Now()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Memory) GetBucketFor(key string) (*LeakyBucket, error) {
|
|
||||||
|
|
||||||
bucket, ok := m.store[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("miss")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bucket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Memory) SetBucketFor(key string, bucket LeakyBucket) error {
|
|
||||||
|
|
||||||
if len(m.store) > GC_SIZE {
|
|
||||||
m.GarbageCollect()
|
|
||||||
}
|
|
||||||
|
|
||||||
m.store[key] = bucket
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Memory) GarbageCollect() {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
// rate limit GC to once per minute
|
|
||||||
if now.Add(60*time.Second).Unix() > m.lastGCCollected.Unix() {
|
|
||||||
|
|
||||||
for key, bucket := range m.store {
|
|
||||||
// if the bucket is drained, then GC
|
|
||||||
if bucket.DrainedAt().Unix() > now.Unix() {
|
|
||||||
delete(m.store, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.lastGCCollected = now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
vendor/github.com/hpcloud/tail/ratelimiter/storage.go
generated
vendored
6
vendor/github.com/hpcloud/tail/ratelimiter/storage.go
generated
vendored
@@ -1,6 +0,0 @@
|
|||||||
package ratelimiter
|
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
GetBucketFor(string) (*LeakyBucket, error)
|
|
||||||
SetBucketFor(string, LeakyBucket) error
|
|
||||||
}
|
|
||||||
438
vendor/github.com/hpcloud/tail/tail.go
generated
vendored
438
vendor/github.com/hpcloud/tail/tail.go
generated
vendored
@@ -1,438 +0,0 @@
|
|||||||
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
|
|
||||||
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
|
|
||||||
|
|
||||||
package tail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hpcloud/tail/ratelimiter"
|
|
||||||
"github.com/hpcloud/tail/util"
|
|
||||||
"github.com/hpcloud/tail/watch"
|
|
||||||
"gopkg.in/tomb.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrStop = fmt.Errorf("tail should now stop")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Line struct {
|
|
||||||
Text string
|
|
||||||
Time time.Time
|
|
||||||
Err error // Error from tail
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLine returns a Line with present time.
|
|
||||||
func NewLine(text string) *Line {
|
|
||||||
return &Line{text, time.Now(), nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeekInfo represents arguments to `os.Seek`
|
|
||||||
type SeekInfo struct {
|
|
||||||
Offset int64
|
|
||||||
Whence int // os.SEEK_*
|
|
||||||
}
|
|
||||||
|
|
||||||
type logger interface {
|
|
||||||
Fatal(v ...interface{})
|
|
||||||
Fatalf(format string, v ...interface{})
|
|
||||||
Fatalln(v ...interface{})
|
|
||||||
Panic(v ...interface{})
|
|
||||||
Panicf(format string, v ...interface{})
|
|
||||||
Panicln(v ...interface{})
|
|
||||||
Print(v ...interface{})
|
|
||||||
Printf(format string, v ...interface{})
|
|
||||||
Println(v ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is used to specify how a file must be tailed.
|
|
||||||
type Config struct {
|
|
||||||
// File-specifc
|
|
||||||
Location *SeekInfo // Seek to this location before tailing
|
|
||||||
ReOpen bool // Reopen recreated files (tail -F)
|
|
||||||
MustExist bool // Fail early if the file does not exist
|
|
||||||
Poll bool // Poll for file changes instead of using inotify
|
|
||||||
Pipe bool // Is a named pipe (mkfifo)
|
|
||||||
RateLimiter *ratelimiter.LeakyBucket
|
|
||||||
|
|
||||||
// Generic IO
|
|
||||||
Follow bool // Continue looking for new lines (tail -f)
|
|
||||||
MaxLineSize int // If non-zero, split longer lines into multiple lines
|
|
||||||
|
|
||||||
// Logger, when nil, is set to tail.DefaultLogger
|
|
||||||
// To disable logging: set field to tail.DiscardingLogger
|
|
||||||
Logger logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tail struct {
|
|
||||||
Filename string
|
|
||||||
Lines chan *Line
|
|
||||||
Config
|
|
||||||
|
|
||||||
file *os.File
|
|
||||||
reader *bufio.Reader
|
|
||||||
|
|
||||||
watcher watch.FileWatcher
|
|
||||||
changes *watch.FileChanges
|
|
||||||
|
|
||||||
tomb.Tomb // provides: Done, Kill, Dying
|
|
||||||
|
|
||||||
lk sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultLogger is used when Config.Logger == nil
|
|
||||||
DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
|
|
||||||
// DiscardingLogger can be used to disable logging output
|
|
||||||
DiscardingLogger = log.New(ioutil.Discard, "", 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
// TailFile begins tailing the file. Output stream is made available
|
|
||||||
// via the `Tail.Lines` channel. To handle errors during tailing,
|
|
||||||
// invoke the `Wait` or `Err` method after finishing reading from the
|
|
||||||
// `Lines` channel.
|
|
||||||
func TailFile(filename string, config Config) (*Tail, error) {
|
|
||||||
if config.ReOpen && !config.Follow {
|
|
||||||
util.Fatal("cannot set ReOpen without Follow.")
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &Tail{
|
|
||||||
Filename: filename,
|
|
||||||
Lines: make(chan *Line),
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
// when Logger was not specified in config, use default logger
|
|
||||||
if t.Logger == nil {
|
|
||||||
t.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Poll {
|
|
||||||
t.watcher = watch.NewPollingFileWatcher(filename)
|
|
||||||
} else {
|
|
||||||
t.watcher = watch.NewInotifyFileWatcher(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.MustExist {
|
|
||||||
var err error
|
|
||||||
t.file, err = OpenFile(t.Filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go t.tailFileSync()
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the file's current position, like stdio's ftell().
|
|
||||||
// But this value is not very accurate.
|
|
||||||
// it may readed one line in the chan(tail.Lines),
|
|
||||||
// so it may lost one line.
|
|
||||||
func (tail *Tail) Tell() (offset int64, err error) {
|
|
||||||
if tail.file == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
offset, err = tail.file.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tail.lk.Lock()
|
|
||||||
defer tail.lk.Unlock()
|
|
||||||
if tail.reader == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
offset -= int64(tail.reader.Buffered())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the tailing activity.
|
|
||||||
func (tail *Tail) Stop() error {
|
|
||||||
tail.Kill(nil)
|
|
||||||
return tail.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopAtEOF stops tailing as soon as the end of the file is reached.
|
|
||||||
func (tail *Tail) StopAtEOF() error {
|
|
||||||
tail.Kill(errStopAtEOF)
|
|
||||||
return tail.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
var errStopAtEOF = errors.New("tail: stop at eof")
|
|
||||||
|
|
||||||
func (tail *Tail) close() {
|
|
||||||
close(tail.Lines)
|
|
||||||
tail.closeFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) closeFile() {
|
|
||||||
if tail.file != nil {
|
|
||||||
tail.file.Close()
|
|
||||||
tail.file = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) reopen() error {
|
|
||||||
tail.closeFile()
|
|
||||||
for {
|
|
||||||
var err error
|
|
||||||
tail.file, err = OpenFile(tail.Filename)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
|
|
||||||
if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
|
|
||||||
if err == tomb.ErrDying {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) readLine() (string, error) {
|
|
||||||
tail.lk.Lock()
|
|
||||||
line, err := tail.reader.ReadString('\n')
|
|
||||||
tail.lk.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
// Note ReadString "returns the data read before the error" in
|
|
||||||
// case of an error, including EOF, so we return it as is. The
|
|
||||||
// caller is expected to process it if err is EOF.
|
|
||||||
return line, err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimRight(line, "\n")
|
|
||||||
|
|
||||||
return line, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) tailFileSync() {
|
|
||||||
defer tail.Done()
|
|
||||||
defer tail.close()
|
|
||||||
|
|
||||||
if !tail.MustExist {
|
|
||||||
// deferred first open.
|
|
||||||
err := tail.reopen()
|
|
||||||
if err != nil {
|
|
||||||
if err != tomb.ErrDying {
|
|
||||||
tail.Kill(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek to requested location on first open of the file.
|
|
||||||
if tail.Location != nil {
|
|
||||||
_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
|
|
||||||
tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
|
|
||||||
if err != nil {
|
|
||||||
tail.Killf("Seek error on %s: %s", tail.Filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tail.openReader()
|
|
||||||
|
|
||||||
var offset int64 = 0
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Read line by line.
|
|
||||||
for {
|
|
||||||
// do not seek in named pipes
|
|
||||||
if !tail.Pipe {
|
|
||||||
// grab the position in case we need to back up in the event of a half-line
|
|
||||||
offset, err = tail.Tell()
|
|
||||||
if err != nil {
|
|
||||||
tail.Kill(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line, err := tail.readLine()
|
|
||||||
|
|
||||||
// Process `line` even if err is EOF.
|
|
||||||
if err == nil {
|
|
||||||
cooloff := !tail.sendLine(line)
|
|
||||||
if cooloff {
|
|
||||||
// Wait a second before seeking till the end of
|
|
||||||
// file when rate limit is reached.
|
|
||||||
msg := fmt.Sprintf(
|
|
||||||
"Too much log activity; waiting a second " +
|
|
||||||
"before resuming tailing")
|
|
||||||
tail.Lines <- &Line{msg, time.Now(), fmt.Errorf(msg)}
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
case <-tail.Dying():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := tail.seekEnd(); err != nil {
|
|
||||||
tail.Kill(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err == io.EOF {
|
|
||||||
if !tail.Follow {
|
|
||||||
if line != "" {
|
|
||||||
tail.sendLine(line)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tail.Follow && line != "" {
|
|
||||||
// this has the potential to never return the last line if
|
|
||||||
// it's not followed by a newline; seems a fair trade here
|
|
||||||
err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
|
|
||||||
if err != nil {
|
|
||||||
tail.Kill(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When EOF is reached, wait for more data to become
|
|
||||||
// available. Wait strategy is based on the `tail.watcher`
|
|
||||||
// implementation (inotify or polling).
|
|
||||||
err := tail.waitForChanges()
|
|
||||||
if err != nil {
|
|
||||||
if err != ErrStop {
|
|
||||||
tail.Kill(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// non-EOF error
|
|
||||||
tail.Killf("Error reading %s: %s", tail.Filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-tail.Dying():
|
|
||||||
if tail.Err() == errStopAtEOF {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForChanges waits until the file has been appended, deleted,
|
|
||||||
// moved or truncated. When moved or deleted - the file will be
|
|
||||||
// reopened if ReOpen is true. Truncated files are always reopened.
|
|
||||||
func (tail *Tail) waitForChanges() error {
|
|
||||||
if tail.changes == nil {
|
|
||||||
pos, err := tail.file.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-tail.changes.Modified:
|
|
||||||
return nil
|
|
||||||
case <-tail.changes.Deleted:
|
|
||||||
tail.changes = nil
|
|
||||||
if tail.ReOpen {
|
|
||||||
// XXX: we must not log from a library.
|
|
||||||
tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
|
|
||||||
if err := tail.reopen(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tail.Logger.Printf("Successfully reopened %s", tail.Filename)
|
|
||||||
tail.openReader()
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
|
|
||||||
return ErrStop
|
|
||||||
}
|
|
||||||
case <-tail.changes.Truncated:
|
|
||||||
// Always reopen truncated files (Follow is true)
|
|
||||||
tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
|
|
||||||
if err := tail.reopen(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
|
|
||||||
tail.openReader()
|
|
||||||
return nil
|
|
||||||
case <-tail.Dying():
|
|
||||||
return ErrStop
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) openReader() {
|
|
||||||
if tail.MaxLineSize > 0 {
|
|
||||||
// add 2 to account for newline characters
|
|
||||||
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
|
|
||||||
} else {
|
|
||||||
tail.reader = bufio.NewReader(tail.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) seekEnd() error {
|
|
||||||
return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tail *Tail) seekTo(pos SeekInfo) error {
|
|
||||||
_, err := tail.file.Seek(pos.Offset, pos.Whence)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
|
|
||||||
}
|
|
||||||
// Reset the read buffer whenever the file is re-seek'ed
|
|
||||||
tail.reader.Reset(tail.file)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendLine sends the line(s) to Lines channel, splitting longer lines
|
|
||||||
// if necessary. Return false if rate limit is reached.
|
|
||||||
func (tail *Tail) sendLine(line string) bool {
|
|
||||||
now := time.Now()
|
|
||||||
lines := []string{line}
|
|
||||||
|
|
||||||
// Split longer lines
|
|
||||||
if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
|
|
||||||
lines = util.PartitionString(line, tail.MaxLineSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
tail.Lines <- &Line{line, now, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tail.Config.RateLimiter != nil {
|
|
||||||
ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
|
|
||||||
if !ok {
|
|
||||||
tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
|
|
||||||
tail.Filename)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup removes inotify watches added by the tail package. This function is
|
|
||||||
// meant to be invoked from a process's exit handler. Linux kernel may not
|
|
||||||
// automatically remove inotify watches after the process exits.
|
|
||||||
func (tail *Tail) Cleanup() {
|
|
||||||
watch.Cleanup(tail.Filename)
|
|
||||||
}
|
|
||||||
11
vendor/github.com/hpcloud/tail/tail_posix.go
generated
vendored
11
vendor/github.com/hpcloud/tail/tail_posix.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build linux darwin freebsd netbsd openbsd
|
|
||||||
|
|
||||||
package tail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func OpenFile(name string) (file *os.File, err error) {
|
|
||||||
return os.Open(name)
|
|
||||||
}
|
|
||||||
521
vendor/github.com/hpcloud/tail/tail_test.go
generated
vendored
521
vendor/github.com/hpcloud/tail/tail_test.go
generated
vendored
@@ -1,521 +0,0 @@
|
|||||||
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
|
|
||||||
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// * repeat all the tests with Poll:true
|
|
||||||
|
|
||||||
package tail
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hpcloud/tail/ratelimiter"
|
|
||||||
"github.com/hpcloud/tail/watch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Clear the temporary test directory
|
|
||||||
err := os.RemoveAll(".test")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
// Use a smaller poll duration for faster test runs. Keep it below
|
|
||||||
// 100ms (which value is used as common delays for tests)
|
|
||||||
watch.POLL_DURATION = 5 * time.Millisecond
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMustExist(t *testing.T) {
|
|
||||||
tail, err := TailFile("/no/such/file", Config{Follow: true, MustExist: true})
|
|
||||||
if err == nil {
|
|
||||||
t.Error("MustExist:true is violated")
|
|
||||||
tail.Stop()
|
|
||||||
}
|
|
||||||
tail, err = TailFile("/no/such/file", Config{Follow: true, MustExist: false})
|
|
||||||
if err != nil {
|
|
||||||
t.Error("MustExist:false is violated")
|
|
||||||
}
|
|
||||||
tail.Stop()
|
|
||||||
_, err = TailFile("README.md", Config{Follow: true, MustExist: true})
|
|
||||||
if err != nil {
|
|
||||||
t.Error("MustExist:true on an existing file is violated")
|
|
||||||
}
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitsForFileToExist(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("waits-for-file-to-exist", t)
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false)
|
|
||||||
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitsForFileToExistRelativePath(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("waits-for-file-to-exist-relative", t)
|
|
||||||
|
|
||||||
oldWD, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
tailTest.Fatal(err)
|
|
||||||
}
|
|
||||||
os.Chdir(tailTest.path)
|
|
||||||
defer os.Chdir(oldWD)
|
|
||||||
|
|
||||||
tail, err := TailFile("test.txt", Config{})
|
|
||||||
if err != nil {
|
|
||||||
tailTest.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false)
|
|
||||||
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
if err := ioutil.WriteFile("test.txt", []byte("hello\nworld\n"), 0600); err != nil {
|
|
||||||
tailTest.Fatal(err)
|
|
||||||
}
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
|
||||||
tail, err := TailFile("_no_such_file", Config{Follow: true, MustExist: false})
|
|
||||||
if err != nil {
|
|
||||||
t.Error("MustExist:false is violated")
|
|
||||||
}
|
|
||||||
if tail.Stop() != nil {
|
|
||||||
t.Error("Should be stoped successfully")
|
|
||||||
}
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStopAtEOF(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("maxlinesize", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nthere\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil})
|
|
||||||
|
|
||||||
// read "hello"
|
|
||||||
line := <-tail.Lines
|
|
||||||
if line.Text != "hello" {
|
|
||||||
t.Errorf("Expected to get 'hello', got '%s' instead", line.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false)
|
|
||||||
tail.StopAtEOF()
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMaxLineSizeFollow(t *testing.T) {
|
|
||||||
// As last file line does not end with newline, it will not be present in tail's output
|
|
||||||
maxLineSize(t, true, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMaxLineSizeNoFollow(t *testing.T) {
|
|
||||||
maxLineSize(t, false, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin", "he"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOver4096ByteLine(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("Over4096ByteLine", t)
|
|
||||||
testString := strings.Repeat("a", 4097)
|
|
||||||
tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false)
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t)
|
|
||||||
testString := strings.Repeat("a", 4097)
|
|
||||||
tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 4097})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false)
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocationFull(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("location-full", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false)
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocationFullDontFollow(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("location-full-dontfollow", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: false, Location: nil})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false)
|
|
||||||
|
|
||||||
// Add more data only after reasonable delay.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.AppendFile("test.txt", "more\ndata\n")
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocationEnd(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("location-end", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{0, os.SEEK_END}})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"more", "data"}, false)
|
|
||||||
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.AppendFile("test.txt", "more\ndata\n")
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocationMiddle(t *testing.T) {
|
|
||||||
// Test reading from middle.
|
|
||||||
tailTest := NewTailTest("location-middle", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{-6, os.SEEK_END}})
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{"world", "more", "data"}, false)
|
|
||||||
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.AppendFile("test.txt", "more\ndata\n")
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The use of polling file watcher could affect file rotation
|
|
||||||
// (detected via renames), so test these explicitly.
|
|
||||||
|
|
||||||
func TestReOpenInotify(t *testing.T) {
|
|
||||||
reOpen(t, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReOpenPolling(t *testing.T) {
|
|
||||||
reOpen(t, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The use of polling file watcher could affect file rotation
|
|
||||||
// (detected via renames), so test these explicitly.
|
|
||||||
|
|
||||||
func TestReSeekInotify(t *testing.T) {
|
|
||||||
reSeek(t, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReSeekPolling(t *testing.T) {
|
|
||||||
reSeek(t, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRateLimiting(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("rate-limiting", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n")
|
|
||||||
config := Config{
|
|
||||||
Follow: true,
|
|
||||||
RateLimiter: ratelimiter.NewLeakyBucket(2, time.Second)}
|
|
||||||
leakybucketFull := "Too much log activity; waiting a second before resuming tailing"
|
|
||||||
tail := tailTest.StartTail("test.txt", config)
|
|
||||||
|
|
||||||
// TODO: also verify that tail resumes after the cooloff period.
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{
|
|
||||||
"hello", "world", "again",
|
|
||||||
leakybucketFull,
|
|
||||||
"more", "data",
|
|
||||||
leakybucketFull}, false)
|
|
||||||
|
|
||||||
// Add more data only after reasonable delay.
|
|
||||||
<-time.After(1200 * time.Millisecond)
|
|
||||||
tailTest.AppendFile("test.txt", "more\ndata\n")
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTell(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("tell-position", t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\nagain\nmore\n")
|
|
||||||
config := Config{
|
|
||||||
Follow: false,
|
|
||||||
Location: &SeekInfo{0, os.SEEK_SET}}
|
|
||||||
tail := tailTest.StartTail("test.txt", config)
|
|
||||||
// read noe line
|
|
||||||
<-tail.Lines
|
|
||||||
offset, err := tail.Tell()
|
|
||||||
if err != nil {
|
|
||||||
tailTest.Errorf("Tell return error: %s", err.Error())
|
|
||||||
}
|
|
||||||
tail.Done()
|
|
||||||
// tail.close()
|
|
||||||
|
|
||||||
config = Config{
|
|
||||||
Follow: false,
|
|
||||||
Location: &SeekInfo{offset, os.SEEK_SET}}
|
|
||||||
tail = tailTest.StartTail("test.txt", config)
|
|
||||||
for l := range tail.Lines {
|
|
||||||
// it may readed one line in the chan(tail.Lines),
|
|
||||||
// so it may lost one line.
|
|
||||||
if l.Text != "world" && l.Text != "again" {
|
|
||||||
tailTest.Fatalf("mismatch; expected world or again, but got %s",
|
|
||||||
l.Text)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tail.Done()
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBlockUntilExists(t *testing.T) {
|
|
||||||
tailTest := NewTailTest("block-until-file-exists", t)
|
|
||||||
config := Config{
|
|
||||||
Follow: true,
|
|
||||||
}
|
|
||||||
tail := tailTest.StartTail("test.txt", config)
|
|
||||||
go func() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
tailTest.CreateFile("test.txt", "hello world\n")
|
|
||||||
}()
|
|
||||||
for l := range tail.Lines {
|
|
||||||
if l.Text != "hello world" {
|
|
||||||
tailTest.Fatalf("mismatch; expected hello world, but got %s",
|
|
||||||
l.Text)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tail.Stop()
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxLineSize(t *testing.T, follow bool, fileContent string, expected []string) {
|
|
||||||
tailTest := NewTailTest("maxlinesize", t)
|
|
||||||
tailTest.CreateFile("test.txt", fileContent)
|
|
||||||
tail := tailTest.StartTail("test.txt", Config{Follow: follow, Location: nil, MaxLineSize: 3})
|
|
||||||
go tailTest.VerifyTailOutput(tail, expected, false)
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
tailTest.Cleanup(tail, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reOpen(t *testing.T, poll bool) {
|
|
||||||
var name string
|
|
||||||
var delay time.Duration
|
|
||||||
if poll {
|
|
||||||
name = "reopen-polling"
|
|
||||||
delay = 300 * time.Millisecond // account for POLL_DURATION
|
|
||||||
} else {
|
|
||||||
name = "reopen-inotify"
|
|
||||||
delay = 100 * time.Millisecond
|
|
||||||
}
|
|
||||||
tailTest := NewTailTest(name, t)
|
|
||||||
tailTest.CreateFile("test.txt", "hello\nworld\n")
|
|
||||||
tail := tailTest.StartTail(
|
|
||||||
"test.txt",
|
|
||||||
Config{Follow: true, ReOpen: true, Poll: poll})
|
|
||||||
content := []string{"hello", "world", "more", "data", "endofworld"}
|
|
||||||
go tailTest.ReadLines(tail, content)
|
|
||||||
|
|
||||||
// deletion must trigger reopen
|
|
||||||
<-time.After(delay)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
<-time.After(delay)
|
|
||||||
tailTest.CreateFile("test.txt", "more\ndata\n")
|
|
||||||
|
|
||||||
// rename must trigger reopen
|
|
||||||
<-time.After(delay)
|
|
||||||
tailTest.RenameFile("test.txt", "test.txt.rotated")
|
|
||||||
<-time.After(delay)
|
|
||||||
tailTest.CreateFile("test.txt", "endofworld\n")
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(delay)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
<-time.After(delay)
|
|
||||||
|
|
||||||
// Do not bother with stopping as it could kill the tomb during
|
|
||||||
// the reading of data written above. Timings can vary based on
|
|
||||||
// test environment.
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func reSeek(t *testing.T, poll bool) {
|
|
||||||
var name string
|
|
||||||
if poll {
|
|
||||||
name = "reseek-polling"
|
|
||||||
} else {
|
|
||||||
name = "reseek-inotify"
|
|
||||||
}
|
|
||||||
tailTest := NewTailTest(name, t)
|
|
||||||
tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n")
|
|
||||||
tail := tailTest.StartTail(
|
|
||||||
"test.txt",
|
|
||||||
Config{Follow: true, ReOpen: false, Poll: poll})
|
|
||||||
|
|
||||||
go tailTest.VerifyTailOutput(tail, []string{
|
|
||||||
"a really long string goes here", "hello", "world", "h311o", "w0r1d", "endofworld"}, false)
|
|
||||||
|
|
||||||
// truncate now
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.TruncateFile("test.txt", "h311o\nw0r1d\nendofworld\n")
|
|
||||||
|
|
||||||
// Delete after a reasonable delay, to give tail sufficient time
|
|
||||||
// to read all lines.
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
tailTest.RemoveFile("test.txt")
|
|
||||||
|
|
||||||
// Do not bother with stopping as it could kill the tomb during
|
|
||||||
// the reading of data written above. Timings can vary based on
|
|
||||||
// test environment.
|
|
||||||
tailTest.Cleanup(tail, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test library
|
|
||||||
|
|
||||||
type TailTest struct {
|
|
||||||
Name string
|
|
||||||
path string
|
|
||||||
done chan struct{}
|
|
||||||
*testing.T
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTailTest(name string, t *testing.T) TailTest {
|
|
||||||
tt := TailTest{name, ".test/" + name, make(chan struct{}), t}
|
|
||||||
err := os.MkdirAll(tt.path, os.ModeTemporary|0700)
|
|
||||||
if err != nil {
|
|
||||||
tt.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) CreateFile(name string, contents string) {
|
|
||||||
err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) RemoveFile(name string) {
|
|
||||||
err := os.Remove(t.path + "/" + name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) RenameFile(oldname string, newname string) {
|
|
||||||
oldname = t.path + "/" + oldname
|
|
||||||
newname = t.path + "/" + newname
|
|
||||||
err := os.Rename(oldname, newname)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) AppendFile(name string, contents string) {
|
|
||||||
f, err := os.OpenFile(t.path+"/"+name, os.O_APPEND|os.O_WRONLY, 0600)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.WriteString(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) TruncateFile(name string, contents string) {
|
|
||||||
f, err := os.OpenFile(t.path+"/"+name, os.O_TRUNC|os.O_WRONLY, 0600)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.WriteString(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) StartTail(name string, config Config) *Tail {
|
|
||||||
tail, err := TailFile(t.path+"/"+name, config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return tail
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) {
|
|
||||||
defer close(t.done)
|
|
||||||
t.ReadLines(tail, lines)
|
|
||||||
// It is important to do this if only EOF is expected
|
|
||||||
// otherwise we could block on <-tail.Lines
|
|
||||||
if expectEOF {
|
|
||||||
line, ok := <-tail.Lines
|
|
||||||
if ok {
|
|
||||||
t.Fatalf("more content from tail: %+v", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) ReadLines(tail *Tail, lines []string) {
|
|
||||||
for idx, line := range lines {
|
|
||||||
tailedLine, ok := <-tail.Lines
|
|
||||||
if !ok {
|
|
||||||
// tail.Lines is closed and empty.
|
|
||||||
err := tail.Err()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("tail ended with error: %v", err)
|
|
||||||
}
|
|
||||||
t.Fatalf("tail ended early; expecting more: %v", lines[idx:])
|
|
||||||
}
|
|
||||||
if tailedLine == nil {
|
|
||||||
t.Fatalf("tail.Lines returned nil; not possible")
|
|
||||||
}
|
|
||||||
// Note: not checking .Err as the `lines` argument is designed
|
|
||||||
// to match error strings as well.
|
|
||||||
if tailedLine.Text != line {
|
|
||||||
t.Fatalf(
|
|
||||||
"unexpected line/err from tail: "+
|
|
||||||
"expecting <<%s>>>, but got <<<%s>>>",
|
|
||||||
line, tailedLine.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TailTest) Cleanup(tail *Tail, stop bool) {
|
|
||||||
<-t.done
|
|
||||||
if stop {
|
|
||||||
tail.Stop()
|
|
||||||
}
|
|
||||||
tail.Cleanup()
|
|
||||||
}
|
|
||||||
12
vendor/github.com/hpcloud/tail/tail_windows.go
generated
vendored
12
vendor/github.com/hpcloud/tail/tail_windows.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package tail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hpcloud/tail/winfile"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func OpenFile(name string) (file *os.File, err error) {
|
|
||||||
return winfile.OpenFile(name, os.O_RDONLY, 0)
|
|
||||||
}
|
|
||||||
48
vendor/github.com/hpcloud/tail/util/util.go
generated
vendored
48
vendor/github.com/hpcloud/tail/util/util.go
generated
vendored
@@ -1,48 +0,0 @@
|
|||||||
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
|
|
||||||
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime/debug"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
*log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)}
|
|
||||||
|
|
||||||
// fatal is like panic except it displays only the current goroutine's stack.
|
|
||||||
func Fatal(format string, v ...interface{}) {
|
|
||||||
// https://github.com/hpcloud/log/blob/master/log.go#L45
|
|
||||||
LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack()))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// partitionString partitions the string into chunks of given size,
|
|
||||||
// with the last chunk of variable size.
|
|
||||||
func PartitionString(s string, chunkSize int) []string {
|
|
||||||
if chunkSize <= 0 {
|
|
||||||
panic("invalid chunkSize")
|
|
||||||
}
|
|
||||||
length := len(s)
|
|
||||||
chunks := 1 + length/chunkSize
|
|
||||||
start := 0
|
|
||||||
end := chunkSize
|
|
||||||
parts := make([]string, 0, chunks)
|
|
||||||
for {
|
|
||||||
if end > length {
|
|
||||||
end = length
|
|
||||||
}
|
|
||||||
parts = append(parts, s[start:end])
|
|
||||||
if end == length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
start, end = end, end+chunkSize
|
|
||||||
}
|
|
||||||
return parts
|
|
||||||
}
|
|
||||||
36
vendor/github.com/hpcloud/tail/watch/filechanges.go
generated
vendored
36
vendor/github.com/hpcloud/tail/watch/filechanges.go
generated
vendored
@@ -1,36 +0,0 @@
|
|||||||
package watch
|
|
||||||
|
|
||||||
type FileChanges struct {
|
|
||||||
Modified chan bool // Channel to get notified of modifications
|
|
||||||
Truncated chan bool // Channel to get notified of truncations
|
|
||||||
Deleted chan bool // Channel to get notified of deletions/renames
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFileChanges() *FileChanges {
|
|
||||||
return &FileChanges{
|
|
||||||
make(chan bool), make(chan bool), make(chan bool)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fc *FileChanges) NotifyModified() {
|
|
||||||
sendOnlyIfEmpty(fc.Modified)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fc *FileChanges) NotifyTruncated() {
|
|
||||||
sendOnlyIfEmpty(fc.Truncated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fc *FileChanges) NotifyDeleted() {
|
|
||||||
sendOnlyIfEmpty(fc.Deleted)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendOnlyIfEmpty sends on a bool channel only if the channel has no
|
|
||||||
// backlog to be read by other goroutines. This concurrency pattern
|
|
||||||
// can be used to notify other goroutines if and only if they are
|
|
||||||
// looking for it (i.e., subsequent notifications can be compressed
|
|
||||||
// into one).
|
|
||||||
func sendOnlyIfEmpty(ch chan bool) {
|
|
||||||
select {
|
|
||||||
case ch <- true:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user