Write termination message
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user