From c9cc1741c7003a1815414ef2d1ceb67f1fab7630 Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Thu, 22 Feb 2018 11:31:42 +0000 Subject: [PATCH] Write termination message --- cmd/runmqserver/logging.go | 23 +++++++++++++-- cmd/runmqserver/main.go | 14 ++++++++-- cmd/runmqserver/mirror.go | 4 +-- internal/command/command.go | 5 ++-- internal/mqini/mqini.go | 12 ++++---- internal/mqini/mqini_test.go | 2 +- test/docker/docker_api_test.go | 10 +++++-- test/docker/docker_api_test_util.go | 43 +++++++++++++++++++++++++++-- 8 files changed, 90 insertions(+), 23 deletions(-) diff --git a/cmd/runmqserver/logging.go b/cmd/runmqserver/logging.go index 3235d7d..f885baa 100644 --- a/cmd/runmqserver/logging.go +++ b/cmd/runmqserver/logging.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "sync" @@ -45,9 +46,9 @@ func (f *simpleTextFormatter) Format(entry *logrus.Entry) ([]byte, error) { return []byte(formatSimple(entry.Time.Format(timestampFormat), entry.Message)), nil } -func logDebug(msg string) { +func logDebug(args ...interface{}) { if debug { - log.Debugln(msg) + log.Debug(args) } } @@ -57,6 +58,22 @@ func logDebugf(format string, args ...interface{}) { } } +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 jsonLogs() bool { e := os.Getenv("MQ_ALPHA_JSON_LOGS") if e == "true" || e == "1" { @@ -78,7 +95,7 @@ func mirrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart // Put the queue manager name in quotes to handle cases like name=.. qm, err := mqini.GetQueueManager(name) if err != nil { - logDebugf("%v", err) + logDebug(err) return nil, err } f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json") diff --git a/cmd/runmqserver/main.go b/cmd/runmqserver/main.go index c56960c..8a24dce 100644 --- a/cmd/runmqserver/main.go +++ b/cmd/runmqserver/main.go @@ -34,19 +34,23 @@ func doMain() error { configureDebugLogger() err := ready.Clear() if err != nil { + logTermination(err) return err } name, err := name.GetQueueManagerName() if err != nil { - log.Println(err) + logTermination(err) return err } accepted, err := checkLicense() if err != nil { + logTerminationf("Error checking license acceptance: %v", err) return err } if !accepted { - return errors.New("License not accepted") + err = errors.New("License not accepted") + logTermination(err) + return err } log.Printf("Using queue manager name: %v", name) @@ -56,7 +60,7 @@ func doMain() error { logConfig() err = createVolume("/mnt/mqm") if err != nil { - log.Println(err) + logTermination(err) return err } err = createDirStructure() @@ -65,6 +69,7 @@ func doMain() error { } newQM, err := createQueueManager(name) if err != nil { + logTermination(err) return err } var wg sync.WaitGroup @@ -80,14 +85,17 @@ func doMain() error { // TODO: Use the error channel _, err = mirrorLogs(ctx, &wg, name, newQM) 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 } configureQueueManager() diff --git a/cmd/runmqserver/mirror.go b/cmd/runmqserver/mirror.go index 0fd1542..9f977dc 100644 --- a/cmd/runmqserver/mirror.go +++ b/cmd/runmqserver/mirror.go @@ -18,6 +18,7 @@ package main import ( "bufio" "context" + "fmt" "os" "sync" "time" @@ -42,7 +43,7 @@ func waitForFile(ctx context.Context, path string) (os.FileInfo, error) { time.Sleep(500 * time.Millisecond) continue } else { - return nil, err + return nil, fmt.Errorf("mirror: unable to get info on file %v", path) } } log.Debugf("File exists: %v, %v", path, fi.Size()) @@ -101,7 +102,6 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b // 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() { diff --git a/internal/command/command.go b/internal/command/command.go index 14d0c9e..0804cf2 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2017 +© 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. @@ -18,6 +18,7 @@ limitations under the License. package command import ( + "fmt" "os/exec" "runtime" "syscall" @@ -38,7 +39,7 @@ func Run(name string, arg ...string) (string, int, error) { if ok && runtime.GOOS == "linux" { status, ok := exiterr.Sys().(syscall.WaitStatus) if ok { - return string(out), status.ExitStatus(), err + return string(out), status.ExitStatus(), fmt.Errorf("%v: %v", name, err) } } return string(out), -1, err diff --git a/internal/mqini/mqini.go b/internal/mqini/mqini.go index 571c77e..d5ad25e 100644 --- a/internal/mqini/mqini.go +++ b/internal/mqini/mqini.go @@ -17,10 +17,10 @@ package mqini import ( "bufio" - "bytes" - "os/exec" "path/filepath" "strings" + + "github.com/ibm-messaging/mq-container/internal/command" ) // QueueManager describe high-level configuration information for a queue manager @@ -33,8 +33,8 @@ type QueueManager struct { } // getQueueManagerFromStanza parses a queue manager stanza -func getQueueManagerFromStanza(stanza []byte) (*QueueManager, error) { - scanner := bufio.NewScanner(bytes.NewReader(stanza)) +func getQueueManagerFromStanza(stanza string) (*QueueManager, error) { + scanner := bufio.NewScanner(strings.NewReader(stanza)) qm := QueueManager{} for scanner.Scan() { l := scanner.Text() @@ -59,9 +59,7 @@ func getQueueManagerFromStanza(stanza []byte) (*QueueManager, error) { // 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 - cmd := exec.Command("dspmqinf", "-o", "stanza", name) - // Run the command and wait for completion - out, err := cmd.CombinedOutput() + out, _, err := command.Run("dspmqinf", "-o", "stanza", name) if err != nil { return nil, err } diff --git a/internal/mqini/mqini_test.go b/internal/mqini/mqini_test.go index ed25c56..ee7c99c 100644 --- a/internal/mqini/mqini_test.go +++ b/internal/mqini/mqini_test.go @@ -39,7 +39,7 @@ func TestGetQueueManager(t *testing.T) { if err != nil { t.Fatal(err) } - qm, err := getQueueManagerFromStanza(b) + qm, err := getQueueManagerFromStanza(string(b)) if err != nil { t.Fatal(err) } diff --git a/test/docker/docker_api_test.go b/test/docker/docker_api_test.go index 5c192b0..842b95b 100644 --- a/test/docker/docker_api_test.go +++ b/test/docker/docker_api_test.go @@ -235,9 +235,9 @@ func TestStartQueueManagerFail(t *testing.T) { oldEntrypoint := strings.Join(img.Config.Entrypoint, " ") containerConfig := container.Config{ Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, - // Override the entrypoint to replace `crtmqm` with a no-op script. - // This will cause `strmqm` to return with an exit code of 16. - Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\n' > /opt/mqm/bin/crtmqm && exec " + oldEntrypoint}, + // 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) @@ -245,6 +245,10 @@ func TestStartQueueManagerFail(t *testing.T) { if rc != 1 { t.Errorf("Expected rc=1, got rc=%v", rc) } + m := terminationMessage(t) + if m == "" { + t.Error("Expected termination message to be set") + } } // TestVolumeUnmount runs a queue manager with a volume, and then forces an diff --git a/test/docker/docker_api_test_util.go b/test/docker/docker_api_test_util.go index 8d0a3e8..7bdd11b 100644 --- a/test/docker/docker_api_test_util.go +++ b/test/docker/docker_api_test_util.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2017 +© 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. @@ -71,6 +71,37 @@ 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 cleanContainer(t *testing.T, cli *client.Client, ID string) { i, err := cli.ContainerInspect(context.Background(), ID) if err == nil { @@ -91,11 +122,18 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) { t.Log(err) } t.Log("Container stopped") + // If a code coverage file has been generated, then rename it to match the test name os.Rename(filepath.Join(coverageDir(t), "container.cov"), filepath.Join(coverageDir(t), t.Name()+".cov")) // Log the container output for any container we're about to delete 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) opts := types.ContainerRemoveOptions{ RemoveVolumes: true, @@ -119,6 +157,7 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C hostConfig := container.HostConfig{ Binds: []string{ coverageBind(t), + terminationBind(t), }, } networkingConfig := network.NetworkingConfig{} @@ -437,10 +476,10 @@ func deleteImage(t *testing.T, cli *client.Client, id string) { func copyFromContainer(t *testing.T, cli *client.Client, id string, file string) []byte { reader, _, err := cli.CopyFromContainer(context.Background(), id, file) - defer reader.Close() if err != nil { t.Fatal(err) } + defer reader.Close() b, err := ioutil.ReadAll(reader) if err != nil { t.Fatal(err)