diff --git a/Dockerfile-server b/Dockerfile-server index 4225dd1..892f539 100644 --- a/Dockerfile-server +++ b/Dockerfile-server @@ -45,12 +45,14 @@ ENV PATH="${PATH}:/opt/mqm/bin" RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\" -X \"main.ImageTag=$IMAGE_TAG\"" ./cmd/runmqserver/ RUN go build ./cmd/chkmqready/ RUN go build ./cmd/chkmqhealthy/ +RUN go build ./cmd/chkmqstarted/ RUN go build ./cmd/runmqdevserver/ RUN go build -buildmode=c-shared -o amqpasdev.so ./internal/qmgrauth/pas.go RUN go test -v ./cmd/runmqdevserver/... RUN go test -v ./cmd/runmqserver/ RUN go test -v ./cmd/chkmqready/ RUN go test -v ./cmd/chkmqhealthy/ +RUN go test -v ./cmd/chkmqstarted/ RUN go test -v ./pkg/... RUN go test -v ./internal/... RUN go vet ./cmd/... ./internal/... diff --git a/cmd/chkmqstarted/main.go b/cmd/chkmqstarted/main.go new file mode 100644 index 0000000..d9964a9 --- /dev/null +++ b/cmd/chkmqstarted/main.go @@ -0,0 +1,72 @@ +/* +© Copyright IBM Corporation 2021 + +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. +*/ + +// chkmqstarted checks that MQ has successfully started, by checking the output of the "dspmq" command +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/ibm-messaging/mq-container/pkg/name" +) + +func queueManagerStarted() (bool, error) { + name, err := name.GetQueueManagerName() + if err != nil { + return false, err + } + // Specify the queue manager name, just in case someone's created a second queue manager + // #nosec G204 + cmd := exec.Command("dspmq", "-n", "-m", name) + // Run the command and wait for completion + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + return false, err + } + if !strings.Contains(string(out), "(RUNNING)") && !strings.Contains(string(out), "(RUNNING AS STANDBY)") && !strings.Contains(string(out), "(STARTING)") && !strings.Contains(string(out), "(REPLICA)") { + return false, nil + } + if os.Getenv("MQ_NATIVE_HA") == "true" { + // Specify the queue manager name, just in case someone's created a second queue manager + // #nosec G204 + cmd = exec.Command("dspmq", "-o", "nativeha", "-m", name) + // Run the command and wait for completion + out, err = cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + return false, err + } + if !strings.Contains(string(out), "INSYNC(yes)") { + return false, nil + } + } + return true, nil +} + +func main() { + started, err := queueManagerStarted() + if err != nil { + os.Exit(2) + } + if !started { + os.Exit(1) + } + os.Exit(0) +} diff --git a/docs/internals.md b/docs/internals.md index 3327b28..b2a0343 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -11,6 +11,7 @@ The resulting Docker image contains the following: - `runmqdevserver` - The main process for MQ Advanced for Developers - `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. + - `chkmqstarted` - Checks if the queue manager has successfully started. This can be used by (say) a Kubernetes startup probe. ## runmqserver The `runmqserver` command has the following responsibilities: diff --git a/test/docker/docker_api_test.go b/test/docker/docker_api_test.go index 3c395fd..7ffd8f7 100644 --- a/test/docker/docker_api_test.go +++ b/test/docker/docker_api_test.go @@ -1475,3 +1475,44 @@ func TestHealthCheckWithNoNewPrivileges(t *testing.T) { func TestHealthCheckWithNewPrivileges(t *testing.T) { utilTestHealthCheck(t, false) } + +// utilTestStartedCheck is used by TestStartedCheck* to run a container with +// privileges enabled or disabled. Otherwise the same as the golden path tests. +func utilTestStartedCheck(t *testing.T, nonewpriv bool) { + t.Parallel() + cli, err := client.NewEnvClient() + if err != nil { + t.Fatal(err) + } + containerConfig := container.Config{ + Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, + } + hostConfig := getDefaultHostConfig(t, cli) + hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("no-new-privileges:%v", nonewpriv)) + id := runContainerWithHostConfig(t, cli, &containerConfig, hostConfig) + defer cleanContainer(t, cli, id) + waitForReady(t, cli, id) + rc, out := execContainer(t, cli, id, "", []string{"chkmqstarted"}) + t.Log(out) + if rc != 0 { + t.Errorf("Expected chkmqstarted to return with exit code 0; got \"%v\"", rc) + t.Logf("Output from chkmqstarted:\n%v", out) + } + // Stop the container cleanly + stopContainer(t, cli, id) +} + +// TestStartedCheckWithNoNewPrivileges tests golden path start/stop plus +// chkmqstarted, when running in a container where no new privileges are +// allowed (i.e. setuid is disabled) +func TestStartedCheckWithNoNewPrivileges(t *testing.T) { + utilTestStartedCheck(t, true) +} + +// TestStartedCheckWithNoNewPrivileges tests golden path start/stop plus +// chkmqstarted when running in a container where new privileges are +// allowed (i.e. setuid is allowed) +// See https://github.com/ibm-messaging/mq-container/issues/428 +func TestStartedCheckWithNewPrivileges(t *testing.T) { + utilTestStartedCheck(t, false) +} diff --git a/test/docker/go.sum b/test/docker/go.sum index ba21f48..ffc2ea8 100644 --- a/test/docker/go.sum +++ b/test/docker/go.sum @@ -219,6 +219,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/test/docker/mq_native_ha_test_util.go b/test/docker/mq_native_ha_test_util.go index c0cdf3c..a94c68d 100644 --- a/test/docker/mq_native_ha_test_util.go +++ b/test/docker/mq_native_ha_test_util.go @@ -116,7 +116,11 @@ func waitForReadyHA(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) { rc, _ := execContainer(t, cli, id, "", []string{"chkmqready"}) if rc == 0 { t.Log("MQ is ready") - return + rc, _ := execContainer(t, cli, id, "", []string{"chkmqstarted"}) + if rc == 0 { + t.Log("MQ has started") + return + } } } case <-ctx.Done():