Write termination message

This commit is contained in:
Arthur Barr
2018-02-22 11:31:42 +00:00
parent d70bbe4dfa
commit c9cc1741c7
8 changed files with 90 additions and 23 deletions

View File

@@ -19,6 +19,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@@ -45,9 +46,9 @@ func (f *simpleTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return []byte(formatSimple(entry.Time.Format(timestampFormat), entry.Message)), nil return []byte(formatSimple(entry.Time.Format(timestampFormat), entry.Message)), nil
} }
func logDebug(msg string) { func logDebug(args ...interface{}) {
if debug { 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 { func jsonLogs() bool {
e := os.Getenv("MQ_ALPHA_JSON_LOGS") e := os.Getenv("MQ_ALPHA_JSON_LOGS")
if e == "true" || e == "1" { 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=.. // Put the queue manager name in quotes to handle cases like name=..
qm, err := mqini.GetQueueManager(name) qm, err := mqini.GetQueueManager(name)
if err != nil { if err != nil {
logDebugf("%v", err) logDebug(err)
return nil, err return nil, err
} }
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json") f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")

View File

@@ -34,19 +34,23 @@ func doMain() error {
configureDebugLogger() configureDebugLogger()
err := ready.Clear() err := ready.Clear()
if err != nil { if err != nil {
logTermination(err)
return err return err
} }
name, err := name.GetQueueManagerName() name, err := name.GetQueueManagerName()
if err != nil { if err != nil {
log.Println(err) logTermination(err)
return err return err
} }
accepted, err := checkLicense() accepted, err := checkLicense()
if err != nil { if err != nil {
logTerminationf("Error checking license acceptance: %v", err)
return err return err
} }
if !accepted { 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) log.Printf("Using queue manager name: %v", name)
@@ -56,7 +60,7 @@ func doMain() error {
logConfig() logConfig()
err = createVolume("/mnt/mqm") err = createVolume("/mnt/mqm")
if err != nil { if err != nil {
log.Println(err) logTermination(err)
return err return err
} }
err = createDirStructure() err = createDirStructure()
@@ -65,6 +69,7 @@ func doMain() error {
} }
newQM, err := createQueueManager(name) newQM, err := createQueueManager(name)
if err != nil { if err != nil {
logTermination(err)
return err return err
} }
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -80,14 +85,17 @@ func doMain() error {
// TODO: Use the error channel // TODO: Use the error channel
_, err = mirrorLogs(ctx, &wg, name, newQM) _, err = mirrorLogs(ctx, &wg, name, newQM)
if err != nil { if err != nil {
logTermination(err)
return err return err
} }
err = updateCommandLevel() err = updateCommandLevel()
if err != nil { if err != nil {
logTermination(err)
return err return err
} }
err = startQueueManager() err = startQueueManager()
if err != nil { if err != nil {
logTermination(err)
return err return err
} }
configureQueueManager() configureQueueManager()

View File

@@ -18,6 +18,7 @@ package main
import ( import (
"bufio" "bufio"
"context" "context"
"fmt"
"os" "os"
"sync" "sync"
"time" "time"
@@ -42,7 +43,7 @@ func waitForFile(ctx context.Context, path string) (os.FileInfo, error) {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
continue continue
} else { } 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()) 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 // File already exists, so start reading at the end
offset = fi.Size() offset = fi.Size()
} }
// Increment wait group counter, only if the goroutine gets started // Increment wait group counter, only if the goroutine gets started
wg.Add(1) wg.Add(1)
go func() { go func() {

View File

@@ -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,6 +18,7 @@ limitations under the License.
package command package command
import ( import (
"fmt"
"os/exec" "os/exec"
"runtime" "runtime"
"syscall" "syscall"
@@ -38,7 +39,7 @@ func Run(name string, arg ...string) (string, int, error) {
if ok && runtime.GOOS == "linux" { if ok && runtime.GOOS == "linux" {
status, ok := exiterr.Sys().(syscall.WaitStatus) status, ok := exiterr.Sys().(syscall.WaitStatus)
if ok { if ok {
return string(out), status.ExitStatus(), err return string(out), status.ExitStatus(), fmt.Errorf("%v: %v", name, err)
} }
} }
return string(out), -1, err return string(out), -1, err

View File

@@ -17,10 +17,10 @@ package mqini
import ( import (
"bufio" "bufio"
"bytes"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/ibm-messaging/mq-container/internal/command"
) )
// QueueManager describe high-level configuration information for a queue manager // QueueManager describe high-level configuration information for a queue manager
@@ -33,8 +33,8 @@ type QueueManager struct {
} }
// getQueueManagerFromStanza parses a queue manager stanza // getQueueManagerFromStanza parses a queue manager stanza
func getQueueManagerFromStanza(stanza []byte) (*QueueManager, error) { func getQueueManagerFromStanza(stanza string) (*QueueManager, error) {
scanner := bufio.NewScanner(bytes.NewReader(stanza)) scanner := bufio.NewScanner(strings.NewReader(stanza))
qm := QueueManager{} qm := QueueManager{}
for scanner.Scan() { for scanner.Scan() {
l := scanner.Text() l := scanner.Text()
@@ -59,9 +59,7 @@ func getQueueManagerFromStanza(stanza []byte) (*QueueManager, error) {
// GetQueueManager returns queue manager configuration information // GetQueueManager returns queue manager configuration information
func GetQueueManager(name string) (*QueueManager, error) { func GetQueueManager(name string) (*QueueManager, error) {
// dspmqinf essentially returns a subset of mqs.ini, but it's simpler to parse // dspmqinf essentially returns a subset of mqs.ini, but it's simpler to parse
cmd := exec.Command("dspmqinf", "-o", "stanza", name) out, _, err := command.Run("dspmqinf", "-o", "stanza", name)
// Run the command and wait for completion
out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -39,7 +39,7 @@ func TestGetQueueManager(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
qm, err := getQueueManagerFromStanza(b) qm, err := getQueueManagerFromStanza(string(b))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -235,9 +235,9 @@ func TestStartQueueManagerFail(t *testing.T) {
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ") oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
// Override the entrypoint to replace `crtmqm` with a no-op script. // 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 16. // This will cause `strmqm` to return with an exit code of 72.
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\n' > /opt/mqm/bin/crtmqm && exec " + oldEntrypoint}, Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && exec " + oldEntrypoint},
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
@@ -245,6 +245,10 @@ func TestStartQueueManagerFail(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)
} }
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 // TestVolumeUnmount runs a queue manager with a volume, and then forces an

View File

@@ -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.
@@ -71,6 +71,37 @@ func coverageBind(t *testing.T) string {
return coverageDir(t) + ":/var/coverage" 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) { func cleanContainer(t *testing.T, cli *client.Client, ID string) {
i, err := cli.ContainerInspect(context.Background(), ID) i, err := cli.ContainerInspect(context.Background(), ID)
if err == nil { if err == nil {
@@ -91,11 +122,18 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
t.Log(err) t.Log(err)
} }
t.Log("Container stopped") t.Log("Container stopped")
// If a code coverage file has been generated, then rename it to match the test name // 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")) 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, inspectTextLogs(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{
RemoveVolumes: true, RemoveVolumes: true,
@@ -119,6 +157,7 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
hostConfig := container.HostConfig{ hostConfig := container.HostConfig{
Binds: []string{ Binds: []string{
coverageBind(t), coverageBind(t),
terminationBind(t),
}, },
} }
networkingConfig := network.NetworkingConfig{} 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 { func copyFromContainer(t *testing.T, cli *client.Client, id string, file string) []byte {
reader, _, err := cli.CopyFromContainer(context.Background(), id, file) reader, _, err := cli.CopyFromContainer(context.Background(), id, file)
defer reader.Close()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer reader.Close()
b, err := ioutil.ReadAll(reader) b, err := ioutil.ReadAll(reader)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)