Initial commit

This commit is contained in:
Arthur Barr
2017-09-07 13:39:11 +01:00
commit ec68367227
735 changed files with 248651 additions and 0 deletions

55
cmd/chkmqhealthy/main.go Normal file
View File

@@ -0,0 +1,55 @@
/*
© 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.
*/
// chkmqhealthy checks that MQ is healthy, by checking the output of the "dspmq" command
package main
import (
"os"
"os/exec"
"strings"
"github.ibm.com/mq-container/mq-container/pkg/name"
)
func queueManagerHealthy() (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
cmd := exec.Command("dspmq", "-n", "-m", name)
// Run the command and wait for completion
out, err := cmd.CombinedOutput()
if err != nil {
return false, err
}
if !strings.Contains(string(out), "(RUNNING)") {
return false, nil
}
return true, nil
}
func main() {
healthy, err := queueManagerHealthy()
if err != nil {
os.Exit(2)
}
if !healthy {
os.Exit(1)
}
os.Exit(0)
}

31
cmd/chkmqready/main.go Normal file
View File

@@ -0,0 +1,31 @@
/*
© 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.
*/
// chkmqready checks that MQ is ready for work, by checking if the MQ listener port is available
package main
import (
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:1414")
if err != nil {
os.Exit(1)
}
conn.Close()
}

View File

@@ -0,0 +1,87 @@
/*
© 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 (
"log"
"os"
"path/filepath"
"runtime"
"syscall"
)
//const mainDir string := "/mnt/mqm"
const mqmUID uint32 = 1000
const mqmGID uint32 = 1000
func createVolume(path string) error {
// fi, err := os.Stat(path)
// if err != nil {
// if os.IsNotExist(err) {
// // TODO: Should this be fatal?
// //log.Warnf("No volume found under %v", path)
// return nil
// } else {
// return err
// }
// }
//log.Printf("%v details: %v", path, fi.Sys())
dataPath := filepath.Join(path, "data")
fi, err := os.Stat(dataPath)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dataPath, 0755)
if err != nil {
return err
}
} else {
return err
}
}
fi, err = os.Stat(dataPath)
if err != nil {
return err
}
sys := fi.Sys()
if sys != nil && runtime.GOOS == "linux" {
// log.Printf("Checking UID/GID for %v", dataPath)
//log.Debugf("Checking UID/GID for %v", dataPath)
stat := sys.(*syscall.Stat_t)
if stat.Uid != mqmUID || stat.Gid != mqmGID {
err = os.Chown(dataPath, int(mqmUID), int(mqmGID))
if err != nil {
log.Printf("Error: Unable to change ownership of %v", dataPath)
return err
}
}
}
return nil
}
// If /mnt/mqm exists
// If /mnt/mqm contains a "data" directory AND data is owned by mqm:mqm AND data is writeable by mqm:mqm then
// Create Symlink from /var/mqm to /mnt/mqm/data
// Else
// // Try to sort it out
// Create directory /mnt/mqm/data
// If directory not already owned by mqm:mqm
// chown mqm:mqm
// if error
// delete directory again if empty
// if directory not already 0755
// chmod 0755
// if error
// delete directory again if empty

331
cmd/runmqserver/main.go Normal file
View File

@@ -0,0 +1,331 @@
/*
© 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.
*/
// runmqserver initializes, creates and starts a queue manager, as PID 1 in a container
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strings"
"syscall"
"github.com/hpcloud/tail"
"golang.org/x/sys/unix"
)
// 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"
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) {
// log.Debugf("Running command %v %v", name, 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" {
// func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error)
var ws unix.WaitStatus
//var rusage syscall.Rusage
unix.Wait4(cmd.Process.Pid, &ws, 0, nil)
//ee := err.(*os.SyscallError)
//ws := ee.Sys().(syscall.WaitStatus)
rc = ws.ExitStatus()
} else {
rc = -1
}
if rc == 0 {
return string(out), rc, nil
}
return string(out), rc, err
}
return string(out), 0, nil
}
// createDirStructure creates the default MQ directory structure under /var/mqm
func createDirStructure() {
out, _, err := runCommand("/opt/mqm/bin/crtmqdir", "-f", "-s")
if err != nil {
log.Fatalf("Error creating directory structure: %v\n", string(out))
}
log.Println("Created directory structure under /var/mqm")
}
func createQueueManager(name string) {
log.Printf("Creating queue manager %v", name)
out, rc, err := runCommand("crtmqm", "-q", "-p", "1414", name)
if err != nil {
// 8=Queue manager exists, which is fine
if rc != 8 {
log.Printf("crtmqm returned %v", rc)
log.Fatalln(string(out))
} else {
log.Printf("Detected existing queue manager %v", name)
return
}
}
}
func updateCommandLevel() {
level, ok := os.LookupEnv("MQ_CMDLEVEL")
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() {
out, rc, err := runCommand("strmqm")
if err != nil {
log.Fatalf("Error %v starting queue manager: %v", rc, string(out))
}
}
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
}
}
}
}()
}
// mirrorLog tails the specified file, and logs each line.
// This is useful for usability, as the container console log can show
// messages from the MQ log.
func mirrorLog(path string) error {
tail, err := tail.TailFile(path, tail.Config{
ReOpen: true,
Follow: true,
Poll: true,
Location: &tail.SeekInfo{
Offset: 0,
// End of file
Whence: 2,
},
Logger: tail.DiscardingLogger,
})
if err != nil {
return err
}
go func() {
for line := range tail.Lines {
// TODO: Unless we parse the message, the timestamp will be (slightly) wrong
if strings.HasPrefix(line.Text, "AMQ") {
// TODO: Extended characters don't print correctly
log.Println(line.Text)
}
}
}()
return nil
}
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)
logConfig()
err = createVolume("/mnt/mqm")
if err != nil {
log.Fatal(err)
}
createDirStructure()
mirrorLog("/var/mqm/qmgrs/" + name + "/errors/AMQERR01.LOG")
createQueueManager(name)
updateCommandLevel()
startQueueManager()
configureQueueManager()
// Wait for terminate signal
<-done
}

View File

@@ -0,0 +1,34 @@
/*
© 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 (
"flag"
"testing"
)
var test *bool
func init() {
test = flag.Bool("test", false, "Set to true when running tests for coverage")
}
// Test started when the test binary is started. Only calls main.
func TestSystem(t *testing.T) {
if *test {
main()
}
}

135
cmd/runmqserver/mqconfig.go Normal file
View File

@@ -0,0 +1,135 @@
/*
© 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 (
"io/ioutil"
"log"
"runtime"
"strings"
"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 {
buf, err := ioutil.ReadFile("/etc/os-release")
if err != nil {
return err
}
lines := strings.Split(string(buf), "\n")
for _, l := range lines {
if strings.HasPrefix(l, "PRETTY_NAME=") {
words := strings.Split(l, "\"")
if len(words) >= 2 {
log.Printf("Base image detected: %v", words[1])
return nil
}
}
}
return nil
}
func readProc(filename string) (value string, err error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf)), nil
}
func readMounts() error {
all, err := readProc("/proc/mounts")
if err != nil {
log.Println("Error: Couldn't read /proc/mounts")
return err
}
lines := strings.Split(all, "\n")
detected := false
for i := range lines {
parts := strings.Split(lines[i], " ")
//dev := parts[0]
mountPoint := parts[1]
fsType := parts[2]
if strings.Contains(mountPoint, "/mnt") {
log.Printf("Detected '%v' volume mounted to %v", fsType, mountPoint)
detected = true
}
}
if !detected {
log.Println("No volume detected. Persistent messages may be lost")
} else {
checkFS("/mnt/mqm")
}
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() {
log.Printf("CPU architecture: %v", runtime.GOARCH)
// TODO: You can't use os.user if you're cross-compiling
// u, err := user.Current()
// if err != nil {
// log.Println(err)
// } else {
// log.Printf("Running as user ID %v (%v) with primary group %v", u.Uid, u.Name, u.Gid)
// }
if runtime.GOOS == "linux" {
var err error
osr, err := readProc("/proc/sys/kernel/osrelease")
if err != nil {
log.Println(err)
} else {
log.Printf("Linux kernel version: %v", osr)
}
logBaseImage()
fileMax, err := readProc("/proc/sys/fs/file-max")
if err != nil {
log.Println(err)
} else {
log.Printf("Maximum file handles: %v", fileMax)
}
readMounts()
} else {
log.Fatalf("Unsupported platform: %v", runtime.GOOS)
}
}