first commit
This commit is contained in:
77
cmd/chkmqhealthy/main.go
Normal file
77
cmd/chkmqhealthy/main.go
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
||||
)
|
||||
|
||||
func queueManagerHealthy(ctx context.Context) (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.CommandContext(ctx, "dspmq", "-n", "-m", name)
|
||||
// Run the command and wait for completion
|
||||
out, err := cmd.CombinedOutput()
|
||||
fmt.Printf("%s", out)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false, err
|
||||
}
|
||||
readyStrings := []string{
|
||||
"(RUNNING)",
|
||||
"(RUNNING AS STANDBY)",
|
||||
"(RECOVERY GROUP LEADER)",
|
||||
"(STARTING)",
|
||||
"(REPLICA)",
|
||||
}
|
||||
for _, checkString := range readyStrings {
|
||||
if strings.Contains(string(out), checkString) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func doMain() int {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
defer cancel()
|
||||
|
||||
healthy, err := queueManagerHealthy(ctx)
|
||||
if err != nil {
|
||||
return 2
|
||||
}
|
||||
if !healthy {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(doMain())
|
||||
}
|
||||
88
cmd/chkmqready/main.go
Normal file
88
cmd/chkmqready/main.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
||||
)
|
||||
|
||||
func doMain() int {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
defer cancel()
|
||||
|
||||
// Check if runmqserver has indicated that it's finished configuration
|
||||
r, err := ready.Check()
|
||||
if !r || err != nil {
|
||||
return 1
|
||||
}
|
||||
name, err := name.GetQueueManagerName()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check if the queue manager has a running listener
|
||||
status, err := ready.Status(ctx, name)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
switch status {
|
||||
case ready.StatusActiveQM:
|
||||
portOpen, err := checkPort("127.0.0.1:1414")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if !portOpen {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case ready.StatusRecoveryQM:
|
||||
fmt.Printf("Detected queue manager running as recovery leader")
|
||||
return 0
|
||||
case ready.StatusStandbyQM:
|
||||
fmt.Printf("Detected queue manager running in standby mode")
|
||||
return 10
|
||||
case ready.StatusReplicaQM:
|
||||
fmt.Printf("Detected queue manager running in replica mode")
|
||||
return 20
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(doMain())
|
||||
}
|
||||
|
||||
func checkPort(address string) (portOpen bool, err error) {
|
||||
var conn net.Conn
|
||||
conn, err = net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
portOpen = true
|
||||
err = conn.Close()
|
||||
return
|
||||
}
|
||||
167
cmd/chkmqstarted/main.go
Normal file
167
cmd/chkmqstarted/main.go
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2021, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
||||
)
|
||||
|
||||
func queueManagerStarted(ctx context.Context) (bool, error) {
|
||||
|
||||
name, err := name.GetQueueManagerName()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
readyStrings := []string{
|
||||
"(RUNNING)",
|
||||
"(RUNNING AS STANDBY)",
|
||||
"(RECOVERY GROUP LEADER)",
|
||||
"(STARTING)",
|
||||
"(REPLICA)",
|
||||
}
|
||||
|
||||
// For Native-HA only, check if the queue manager instance is in-sync with one or more replicas
|
||||
// - If not in-sync within the expected time period, revert to checking on queue manager 'ready' status
|
||||
// - This ensures we do not block indefinitely for breaking changes (i.e. protocol changes)
|
||||
if os.Getenv("MQ_NATIVE_HA") == "true" {
|
||||
|
||||
// Check if the Native-HA queue manager instance is currently in-sync
|
||||
isReadyToSync, isInSync, err := isInSyncWithReplicas(ctx, name, readyStrings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if isInSync {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check if the Native-HA queue manager instance is ready-to-sync
|
||||
// - A successful queue manager 'ready' status indicates that we are ready-to-sync
|
||||
if !isReadyToSync {
|
||||
return false, nil
|
||||
}
|
||||
err = ready.SetReadyToSync()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the time period for checking in-sync has now expired
|
||||
// - We have already confirmed a successful queue manager 'ready' status
|
||||
// - Therefore the expiration of the in-sync time period will result in success
|
||||
expired, err := hasInSyncTimePeriodExpired()
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if expired {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Specify the queue manager name, just in case someone's created a second queue manager
|
||||
// #nosec G204
|
||||
cmd := exec.CommandContext(ctx, "dspmq", "-n", "-m", name)
|
||||
// Run the command and wait for completion
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, checkString := range readyStrings {
|
||||
if strings.Contains(string(out), checkString) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isInSyncWithReplicas returns the in-sync status for a Native-HA queue manager instance
|
||||
func isInSyncWithReplicas(ctx context.Context, name string, readyStrings []string) (bool, bool, error) {
|
||||
|
||||
cmd := exec.CommandContext(ctx, "dspmq", "-n", "-o", "nativeha", "-m", name)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
} else if strings.Contains(string(out), "INSYNC(YES)") {
|
||||
return true, true, nil
|
||||
}
|
||||
|
||||
for _, checkString := range readyStrings {
|
||||
if strings.Contains(string(out), checkString) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
// hasInSyncTimePeriodExpired returns true if a Native-HA queue manager instance is not in-sync within the expected time period, otherwise false
|
||||
func hasInSyncTimePeriodExpired() (bool, error) {
|
||||
|
||||
// Default timeout 5 seconds
|
||||
var timeout int64 = 5
|
||||
var err error
|
||||
|
||||
// Check if a timeout override has been set
|
||||
customTimeout := os.Getenv("MQ_NATIVE_HA_IN_SYNC_TIMEOUT")
|
||||
if customTimeout != "" {
|
||||
timeout, err = strconv.ParseInt(customTimeout, 10, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
isReadyToSync, readyToSyncStartTime, err := ready.GetReadyToSyncStartTime()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if isReadyToSync && time.Now().Unix()-readyToSyncStartTime.Unix() >= timeout {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func doMain() int {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
defer cancel()
|
||||
|
||||
started, err := queueManagerStarted(ctx)
|
||||
if err != nil {
|
||||
return 2
|
||||
}
|
||||
if !started {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(doMain())
|
||||
}
|
||||
179
cmd/runmqdevserver/main.go
Normal file
179
cmd/runmqdevserver/main.go
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/copy"
|
||||
"github.com/ibm-messaging/mq-container/internal/simpleauth"
|
||||
"github.com/ibm-messaging/mq-container/pkg/containerruntimelogger"
|
||||
"github.com/ibm-messaging/mq-container/pkg/logger"
|
||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
||||
)
|
||||
|
||||
var log *logger.Logger
|
||||
|
||||
func getLogFormat() string {
|
||||
logFormat := strings.ToLower(strings.TrimSpace(os.Getenv("MQ_LOGGING_CONSOLE_FORMAT")))
|
||||
//old-style env var is used.
|
||||
if logFormat == "" {
|
||||
logFormat = strings.ToLower(strings.TrimSpace(os.Getenv("LOG_FORMAT")))
|
||||
}
|
||||
|
||||
if logFormat != "" && (logFormat == "basic" || logFormat == "json") {
|
||||
return logFormat
|
||||
} else {
|
||||
//this is the case where value is either empty string or set to something other than "basic"/"json"
|
||||
logFormat = "basic"
|
||||
}
|
||||
|
||||
return logFormat
|
||||
}
|
||||
|
||||
func getDebug() bool {
|
||||
debug := os.Getenv("DEBUG")
|
||||
if debug == "true" || debug == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func configureLogger() error {
|
||||
var err error
|
||||
f := getLogFormat()
|
||||
d := getDebug()
|
||||
n, err := name.GetQueueManagerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch f {
|
||||
case "json":
|
||||
log, err = logger.NewLogger(os.Stderr, d, true, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "basic":
|
||||
log, err = logger.NewLogger(os.Stderr, d, false, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log, err = logger.NewLogger(os.Stdout, d, false, n)
|
||||
return fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logTerminationf(format string, args ...interface{}) {
|
||||
logTermination(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// TODO: Duplicated code
|
||||
func logTermination(args ...interface{}) {
|
||||
msg := fmt.Sprint(args...)
|
||||
// Write the message to the termination log. This is not the default place
|
||||
// that Kubernetes will look for termination information.
|
||||
log.Debugf("Writing termination message: %v", msg)
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err := os.WriteFile("/run/termination-log", []byte(msg), 0660)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
log.Error(msg)
|
||||
}
|
||||
|
||||
func doMain() error {
|
||||
err := configureLogger()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialise 10-dev.mqsc file on ephemeral volume
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err = os.WriteFile("/run/10-dev.mqsc", []byte(""), 0660)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialise 20-dev-tls.mqsc file on ephemeral volume
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err = os.WriteFile("/run/20-dev-tls.mqsc", []byte(""), 0660)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialise /run/qm-service-component.ini file on ephemeral volume
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err = os.WriteFile("/run/qm-service-component.ini", []byte(""), 0660)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable mq simpleauth if MQ_CONNAUTH_USE_HTP is set true
|
||||
// and either or both of MQ_APP_PASSWORD and MQ_ADMIN_PASSWORD
|
||||
// environment variables specified.
|
||||
enableHtPwd, set := os.LookupEnv("MQ_CONNAUTH_USE_HTP")
|
||||
if set && strings.EqualFold(enableHtPwd, "true") {
|
||||
err := copy.CopyFile("/etc/mqm/qm-service-component.ini.default", "/run/qm-service-component.ini")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = simpleauth.CheckForPasswords(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = updateMQSC(set)
|
||||
if err != nil {
|
||||
logTerminationf("Error updating MQSC: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var osExit = os.Exit
|
||||
|
||||
func main() {
|
||||
err := doMain()
|
||||
if err != nil {
|
||||
osExit(1)
|
||||
} else {
|
||||
// Replace this process with runmqserver
|
||||
// #nosec G204
|
||||
err = syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver", "-nologruntime", "-dev"}, os.Environ())
|
||||
if err != nil {
|
||||
log.Errorf("Error replacing this process with runmqserver: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
45
cmd/runmqdevserver/mqsc.go
Normal file
45
cmd/runmqdevserver/mqsc.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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 (
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
)
|
||||
|
||||
func updateMQSC(appPasswordRequired bool) error {
|
||||
|
||||
var checkClient string
|
||||
if appPasswordRequired {
|
||||
checkClient = "REQUIRED"
|
||||
} else {
|
||||
checkClient = "ASQMGR"
|
||||
}
|
||||
|
||||
const mqscLink string = "/run/10-dev.mqsc"
|
||||
const mqscTemplate string = "/etc/mqm/10-dev.mqsc.tpl"
|
||||
|
||||
if os.Getenv("MQ_DEV") == "true" {
|
||||
// Re-configure channel if app password not set
|
||||
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqscLink, map[string]string{"ChckClnt": checkClient}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
57
cmd/runmqserver/crtmqvol.go
Normal file
57
cmd/runmqserver/crtmqvol.go
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2023
|
||||
|
||||
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 (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func createVolume(dataPath string) error {
|
||||
_, err := os.Stat(dataPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// #nosec G301
|
||||
err = os.MkdirAll(dataPath, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete files/directories from specified path
|
||||
func cleanVolume(cleanPath string) error {
|
||||
// #nosec G304
|
||||
dirContents, err := os.ReadDir(cleanPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range dirContents {
|
||||
err = os.RemoveAll(filepath.Join(cleanPath, name.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
92
cmd/runmqserver/license.go
Normal file
92
cmd/runmqserver/license.go
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2023
|
||||
|
||||
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 (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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"
|
||||
// Differentiate Czech (cs) and Kashubian (csb)
|
||||
case strings.HasPrefix(lang, "cs") && !strings.HasPrefix(lang, "csb"):
|
||||
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"
|
||||
// Differentiate Korean (ko) from Konkani (kok)
|
||||
case strings.HasPrefix(lang, "ko") && !strings.HasPrefix(lang, "kok"):
|
||||
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() (bool, error) {
|
||||
lic, ok := os.LookupEnv("LICENSE")
|
||||
switch {
|
||||
case ok && lic == "accept":
|
||||
return true, nil
|
||||
case ok && lic == "view":
|
||||
file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile())
|
||||
// #nosec G304
|
||||
buf, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
log.Println(string(buf))
|
||||
return false, nil
|
||||
}
|
||||
log.Println("Error: Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions.")
|
||||
log.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.")
|
||||
return false, errors.New("Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions")
|
||||
}
|
||||
282
cmd/runmqserver/license_test.go
Normal file
282
cmd/runmqserver/license_test.go
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
© 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 (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var licenseTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"en_US.UTF_8", "English.txt"},
|
||||
{"en_US.ISO-8859-15", "English.txt"},
|
||||
{"es_GB", "Spanish.txt"},
|
||||
{"el_ES.UTF_8", "Greek.txt"},
|
||||
// Cover a wide variety of valid values
|
||||
{"af", "English.txt"},
|
||||
{"af_ZA", "English.txt"},
|
||||
{"ar", "English.txt"},
|
||||
{"ar_AE", "English.txt"},
|
||||
{"ar_BH", "English.txt"},
|
||||
{"ar_DZ", "English.txt"},
|
||||
{"ar_EG", "English.txt"},
|
||||
{"ar_IQ", "English.txt"},
|
||||
{"ar_JO", "English.txt"},
|
||||
{"ar_KW", "English.txt"},
|
||||
{"ar_LB", "English.txt"},
|
||||
{"ar_LY", "English.txt"},
|
||||
{"ar_MA", "English.txt"},
|
||||
{"ar_OM", "English.txt"},
|
||||
{"ar_QA", "English.txt"},
|
||||
{"ar_SA", "English.txt"},
|
||||
{"ar_SY", "English.txt"},
|
||||
{"ar_TN", "English.txt"},
|
||||
{"ar_YE", "English.txt"},
|
||||
{"az", "English.txt"},
|
||||
{"az_AZ", "English.txt"},
|
||||
{"az_AZ", "English.txt"},
|
||||
{"be", "English.txt"},
|
||||
{"be_BY", "English.txt"},
|
||||
{"bg", "English.txt"},
|
||||
{"bg_BG", "English.txt"},
|
||||
{"bs_BA", "English.txt"},
|
||||
{"ca", "English.txt"},
|
||||
{"ca_ES", "English.txt"},
|
||||
{"cs", "Czech.txt"},
|
||||
{"cs_CZ", "Czech.txt"},
|
||||
{"csb_PL", "English.txt"},
|
||||
{"cy", "English.txt"},
|
||||
{"cy_GB", "English.txt"},
|
||||
{"da", "English.txt"},
|
||||
{"da_DK", "English.txt"},
|
||||
{"de", "German.txt"},
|
||||
{"de_AT", "German.txt"},
|
||||
{"de_CH", "German.txt"},
|
||||
{"de_DE", "German.txt"},
|
||||
{"de_LI", "German.txt"},
|
||||
{"de_LU", "German.txt"},
|
||||
{"dv", "English.txt"},
|
||||
{"dv_MV", "English.txt"},
|
||||
{"el", "Greek.txt"},
|
||||
{"el_GR", "Greek.txt"},
|
||||
{"en", "English.txt"},
|
||||
{"en_AU", "English.txt"},
|
||||
{"en_BZ", "English.txt"},
|
||||
{"en_CA", "English.txt"},
|
||||
{"en_CB", "English.txt"},
|
||||
{"en_GB", "English.txt"},
|
||||
{"en_IE", "English.txt"},
|
||||
{"en_JM", "English.txt"},
|
||||
{"en_NZ", "English.txt"},
|
||||
{"en_PH", "English.txt"},
|
||||
{"en_TT", "English.txt"},
|
||||
{"en_US", "English.txt"},
|
||||
{"en_ZA", "English.txt"},
|
||||
{"en_ZW", "English.txt"},
|
||||
{"eo", "English.txt"},
|
||||
{"es", "Spanish.txt"},
|
||||
{"es_AR", "Spanish.txt"},
|
||||
{"es_BO", "Spanish.txt"},
|
||||
{"es_CL", "Spanish.txt"},
|
||||
{"es_CO", "Spanish.txt"},
|
||||
{"es_CR", "Spanish.txt"},
|
||||
{"es_DO", "Spanish.txt"},
|
||||
{"es_EC", "Spanish.txt"},
|
||||
{"es_ES", "Spanish.txt"},
|
||||
{"es_ES", "Spanish.txt"},
|
||||
{"es_GT", "Spanish.txt"},
|
||||
{"es_HN", "Spanish.txt"},
|
||||
{"es_MX", "Spanish.txt"},
|
||||
{"es_NI", "Spanish.txt"},
|
||||
{"es_PA", "Spanish.txt"},
|
||||
{"es_PE", "Spanish.txt"},
|
||||
{"es_PR", "Spanish.txt"},
|
||||
{"es_PY", "Spanish.txt"},
|
||||
{"es_SV", "Spanish.txt"},
|
||||
{"es_UY", "Spanish.txt"},
|
||||
{"es_VE", "Spanish.txt"},
|
||||
{"et", "English.txt"},
|
||||
{"et_EE", "English.txt"},
|
||||
{"eu", "English.txt"},
|
||||
{"eu_ES", "English.txt"},
|
||||
{"fa", "English.txt"},
|
||||
{"fa_IR", "English.txt"},
|
||||
{"fi", "English.txt"},
|
||||
{"fi_FI", "English.txt"},
|
||||
{"fo", "English.txt"},
|
||||
{"fo_FO", "English.txt"},
|
||||
{"fr", "French.txt"},
|
||||
{"fr_BE", "French.txt"},
|
||||
{"fr_CA", "French.txt"},
|
||||
{"fr_CH", "French.txt"},
|
||||
{"fr_FR", "French.txt"},
|
||||
{"fr_LU", "French.txt"},
|
||||
{"fr_MC", "French.txt"},
|
||||
{"gl", "English.txt"},
|
||||
{"gl_ES", "English.txt"},
|
||||
{"gu", "English.txt"},
|
||||
{"gu_IN", "English.txt"},
|
||||
{"he", "English.txt"},
|
||||
{"he_IL", "English.txt"},
|
||||
{"hi", "English.txt"},
|
||||
{"hi_IN", "English.txt"},
|
||||
{"hr", "English.txt"},
|
||||
{"hr_BA", "English.txt"},
|
||||
{"hr_HR", "English.txt"},
|
||||
{"hu", "English.txt"},
|
||||
{"hu_HU", "English.txt"},
|
||||
{"hy", "English.txt"},
|
||||
{"hy_AM", "English.txt"},
|
||||
{"id", "Indonesian.txt"},
|
||||
{"id_ID", "Indonesian.txt"},
|
||||
{"is", "English.txt"},
|
||||
{"is_IS", "English.txt"},
|
||||
{"it", "Italian.txt"},
|
||||
{"it_CH", "Italian.txt"},
|
||||
{"it_IT", "Italian.txt"},
|
||||
{"ja", "Japanese.txt"},
|
||||
{"ja_JP", "Japanese.txt"},
|
||||
{"ka", "English.txt"},
|
||||
{"ka_GE", "English.txt"},
|
||||
{"kk", "English.txt"},
|
||||
{"kk_KZ", "English.txt"},
|
||||
{"kn", "English.txt"},
|
||||
{"kn_IN", "English.txt"},
|
||||
{"ko", "Korean.txt"},
|
||||
{"ko_KR", "Korean.txt"},
|
||||
{"kok", "English.txt"},
|
||||
{"kok_IN", "English.txt"},
|
||||
{"ky", "English.txt"},
|
||||
{"ky_KG", "English.txt"},
|
||||
{"lt", "Lithuanian.txt"},
|
||||
{"lt_LT", "Lithuanian.txt"},
|
||||
{"lv", "English.txt"},
|
||||
{"lv_LV", "English.txt"},
|
||||
{"mi", "English.txt"},
|
||||
{"mi_NZ", "English.txt"},
|
||||
{"mk", "English.txt"},
|
||||
{"mk_MK", "English.txt"},
|
||||
{"mn", "English.txt"},
|
||||
{"mn_MN", "English.txt"},
|
||||
{"mr", "English.txt"},
|
||||
{"mr_IN", "English.txt"},
|
||||
{"ms", "English.txt"},
|
||||
{"ms_BN", "English.txt"},
|
||||
{"ms_MY", "English.txt"},
|
||||
{"mt", "English.txt"},
|
||||
{"mt_MT", "English.txt"},
|
||||
{"nb", "English.txt"},
|
||||
{"nb_NO", "English.txt"},
|
||||
{"nl", "English.txt"},
|
||||
{"nl_BE", "English.txt"},
|
||||
{"nl_NL", "English.txt"},
|
||||
{"nn_NO", "English.txt"},
|
||||
{"ns", "English.txt"},
|
||||
{"ns_ZA", "English.txt"},
|
||||
{"pa", "English.txt"},
|
||||
{"pa_IN", "English.txt"},
|
||||
{"pl", "Polish.txt"},
|
||||
{"pl_PL", "Polish.txt"},
|
||||
{"ps", "English.txt"},
|
||||
{"ps_AR", "English.txt"},
|
||||
{"pt", "Portugese.txt"},
|
||||
{"pt_BR", "Portugese.txt"},
|
||||
{"pt_PT", "Portugese.txt"},
|
||||
{"qu", "English.txt"},
|
||||
{"qu_BO", "English.txt"},
|
||||
{"qu_EC", "English.txt"},
|
||||
{"qu_PE", "English.txt"},
|
||||
{"ro", "English.txt"},
|
||||
{"ro_RO", "English.txt"},
|
||||
{"ru", "Russian.txt"},
|
||||
{"ru_RU", "Russian.txt"},
|
||||
{"sa", "English.txt"},
|
||||
{"sa_IN", "English.txt"},
|
||||
{"se", "English.txt"},
|
||||
{"se_FI", "English.txt"},
|
||||
{"se_FI", "English.txt"},
|
||||
{"se_FI", "English.txt"},
|
||||
{"se_NO", "English.txt"},
|
||||
{"se_NO", "English.txt"},
|
||||
{"se_NO", "English.txt"},
|
||||
{"se_SE", "English.txt"},
|
||||
{"se_SE", "English.txt"},
|
||||
{"se_SE", "English.txt"},
|
||||
{"sk", "English.txt"},
|
||||
{"sk_SK", "English.txt"},
|
||||
{"sl", "Slovenian.txt"},
|
||||
{"sl_SI", "Slovenian.txt"},
|
||||
{"sq", "English.txt"},
|
||||
{"sq_AL", "English.txt"},
|
||||
{"sr_BA", "English.txt"},
|
||||
{"sr_BA", "English.txt"},
|
||||
{"sr_SP", "English.txt"},
|
||||
{"sr_SP", "English.txt"},
|
||||
{"sv", "English.txt"},
|
||||
{"sv_FI", "English.txt"},
|
||||
{"sv_SE", "English.txt"},
|
||||
{"sw", "English.txt"},
|
||||
{"sw_KE", "English.txt"},
|
||||
{"syr", "English.txt"},
|
||||
{"syr_SY", "English.txt"},
|
||||
{"ta", "English.txt"},
|
||||
{"ta_IN", "English.txt"},
|
||||
{"te", "English.txt"},
|
||||
{"te_IN", "English.txt"},
|
||||
{"th", "English.txt"},
|
||||
{"th_TH", "English.txt"},
|
||||
{"tl", "English.txt"},
|
||||
{"tl_PH", "English.txt"},
|
||||
{"tn", "English.txt"},
|
||||
{"tn_ZA", "English.txt"},
|
||||
{"tr", "Turkish.txt"},
|
||||
{"tr_TR", "Turkish.txt"},
|
||||
{"tt", "English.txt"},
|
||||
{"tt_RU", "English.txt"},
|
||||
{"ts", "English.txt"},
|
||||
{"uk", "English.txt"},
|
||||
{"uk_UA", "English.txt"},
|
||||
{"ur", "English.txt"},
|
||||
{"ur_PK", "English.txt"},
|
||||
{"uz", "English.txt"},
|
||||
{"uz_UZ", "English.txt"},
|
||||
{"uz_UZ", "English.txt"},
|
||||
{"vi", "English.txt"},
|
||||
{"vi_VN", "English.txt"},
|
||||
{"xh", "English.txt"},
|
||||
{"xh_ZA", "English.txt"},
|
||||
{"zh", "Chinese.txt"},
|
||||
{"zh_CN", "Chinese.txt"},
|
||||
{"zh_HK", "Chinese.txt"},
|
||||
{"zh_MO", "Chinese.txt"},
|
||||
{"zh_SG", "Chinese.txt"},
|
||||
{"zh_TW", "Chinese_TW.txt"},
|
||||
{"zu", "English.txt"},
|
||||
{"zu_ZA", "English.txt"},
|
||||
}
|
||||
|
||||
func TestResolveLicenseFile(t *testing.T) {
|
||||
for _, table := range licenseTests {
|
||||
os.Setenv("LANG", table.in)
|
||||
f := resolveLicenseFile()
|
||||
if f != table.out {
|
||||
t.Errorf("resolveLicenseFile() with LANG=%v - expected %v, got %v", table.in, table.out, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
485
cmd/runmqserver/logging.go
Normal file
485
cmd/runmqserver/logging.go
Normal file
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/pkg/logger"
|
||||
"github.com/ibm-messaging/mq-container/pkg/mqini"
|
||||
)
|
||||
|
||||
// var debug = false
|
||||
var log *logger.Logger
|
||||
|
||||
var collectDiagOnFail = false
|
||||
|
||||
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 not the default place
|
||||
// that Kubernetes will look for termination information.
|
||||
log.Debugf("Writing termination message: %v", msg)
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err := os.WriteFile("/run/termination-log", []byte(msg), 0660)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
log.Error(msg)
|
||||
|
||||
if collectDiagOnFail {
|
||||
logDiagnostics()
|
||||
}
|
||||
}
|
||||
|
||||
func getLogFormat() string {
|
||||
logFormat := strings.ToLower(strings.TrimSpace(os.Getenv("MQ_LOGGING_CONSOLE_FORMAT")))
|
||||
//old-style env var is used.
|
||||
if logFormat == "" {
|
||||
logFormat = strings.ToLower(strings.TrimSpace(os.Getenv("LOG_FORMAT")))
|
||||
}
|
||||
|
||||
if logFormat != "" && (logFormat == "basic" || logFormat == "json") {
|
||||
return logFormat
|
||||
} else {
|
||||
//this is the case where value is either empty string or set to something other than "basic"/"json"
|
||||
logFormat = "basic"
|
||||
}
|
||||
|
||||
return logFormat
|
||||
}
|
||||
|
||||
// formatBasic formats a log message parsed from JSON, as "basic" text
|
||||
func formatBasic(obj map[string]interface{}) string {
|
||||
// Emulate the MQ "MessageDetail=Extended" option, by appending inserts to the message
|
||||
// This is important for certain messages, where key details are only available in the extended message content
|
||||
inserts := make([]string, 0)
|
||||
for k, v := range obj {
|
||||
if strings.HasPrefix(k, "ibm_commentInsert") {
|
||||
inserts = append(inserts, fmt.Sprintf("%s(%v)", strings.Replace(k, "ibm_comment", "Comment", 1), obj[k]))
|
||||
} else if strings.HasPrefix(k, "ibm_arithInsert") {
|
||||
if v.(float64) != 0 {
|
||||
inserts = append(inserts, fmt.Sprintf("%s(%v)", strings.Replace(k, "ibm_arith", "Arith", 1), obj[k]))
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(inserts)
|
||||
if len(inserts) > 0 {
|
||||
return fmt.Sprintf("%s %s [%v]\n", obj["ibm_datetime"], obj["message"], strings.Join(inserts, ", "))
|
||||
}
|
||||
// Convert time zone information from some logs (e.g. Liberty) for consistency
|
||||
obj["ibm_datetime"] = strings.Replace(obj["ibm_datetime"].(string), "+0000", "Z", 1)
|
||||
// Escape any new-line characters, so that we don't get multi-line messages messing up the output
|
||||
obj["message"] = strings.ReplaceAll(obj["message"].(string), "\n", "\\n")
|
||||
|
||||
if obj["type"] != nil && (obj["type"] == "liberty_trace") {
|
||||
timeStamp := obj["ibm_datetime"]
|
||||
threadID := ""
|
||||
srtModuleName := ""
|
||||
logLevel := ""
|
||||
ibmClassName := ""
|
||||
srtIbmClassName := ""
|
||||
ibmMethodName := ""
|
||||
message := ""
|
||||
|
||||
if obj["loglevel"] != nil {
|
||||
//threadID is captured below
|
||||
if obj["ibm_threadId"] != nil {
|
||||
threadID = obj["ibm_threadId"].(string)
|
||||
}
|
||||
|
||||
//logLevel character to be mirrored in console web server logging is decided below
|
||||
logLevelTmp := obj["loglevel"].(string)
|
||||
switch logLevelTmp {
|
||||
case "AUDIT":
|
||||
logLevel = "A"
|
||||
case "INFO":
|
||||
logLevel = "I"
|
||||
case "EVENT":
|
||||
logLevel = "1"
|
||||
case "ENTRY":
|
||||
logLevel = ">"
|
||||
case "EXIT":
|
||||
logLevel = "<"
|
||||
case "FINE":
|
||||
logLevel = "1"
|
||||
case "FINER":
|
||||
logLevel = "2"
|
||||
case "FINEST":
|
||||
logLevel = "3"
|
||||
default:
|
||||
logLevel = string(logLevelTmp[0])
|
||||
}
|
||||
|
||||
//This is a 13 characters string present in extracted out of module node
|
||||
if obj["module"] != nil {
|
||||
srtModuleNameArr := strings.Split(obj["module"].(string), ".")
|
||||
arrLen := len(srtModuleNameArr)
|
||||
srtModuleName = srtModuleNameArr[arrLen-1]
|
||||
if len(srtModuleName) > 13 {
|
||||
srtModuleName = srtModuleName[0:13]
|
||||
}
|
||||
}
|
||||
if obj["ibm_className"] != nil {
|
||||
ibmClassName = obj["ibm_className"].(string)
|
||||
|
||||
//A 13 character string is extracted from class name. This is required for FINE, FINER & FINEST log lines
|
||||
ibmClassNameArr := strings.Split(ibmClassName, ".")
|
||||
arrLen := len(ibmClassNameArr)
|
||||
srtIbmClassName = ibmClassNameArr[arrLen-1]
|
||||
if len(srtModuleName) > 13 {
|
||||
srtIbmClassName = srtIbmClassName[0:13]
|
||||
}
|
||||
}
|
||||
if obj["ibm_methodName"] != nil {
|
||||
ibmMethodName = obj["ibm_methodName"].(string)
|
||||
}
|
||||
if obj["message"] != nil {
|
||||
message = obj["message"].(string)
|
||||
}
|
||||
|
||||
//For AUDIT & INFO logging
|
||||
if logLevel == "A" || logLevel == "I" {
|
||||
return fmt.Sprintf("%s %s %-13s %s %s %s %s\n", timeStamp, threadID, srtModuleName, logLevel, ibmClassName, ibmMethodName, message)
|
||||
}
|
||||
//For EVENT logLevel
|
||||
if logLevelTmp == "EVENT" {
|
||||
return fmt.Sprintf("%s %s %-13s %s %s\n", timeStamp, threadID, srtModuleName, logLevel, message)
|
||||
}
|
||||
//For ENTRY & EXIT
|
||||
if logLevel == ">" || logLevel == "<" {
|
||||
return fmt.Sprintf("%s %s %-13s %s %s %s\n", timeStamp, threadID, srtModuleName, logLevel, ibmMethodName, message)
|
||||
}
|
||||
//For deeper log levels
|
||||
if logLevelTmp == "FINE" || logLevel == "2" || logLevel == "3" {
|
||||
return fmt.Sprintf("%s %s %-13s %s %s %s %s\n", timeStamp, threadID, srtIbmClassName, logLevel, ibmClassName, ibmMethodName, message)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s %s\n", obj["ibm_datetime"], obj["message"])
|
||||
}
|
||||
|
||||
// mirrorSystemErrorLogs starts a goroutine to mirror the contents of the MQ system error logs
|
||||
func mirrorSystemErrorLogs(ctx context.Context, wg *sync.WaitGroup, mf mirrorFunc) (chan error, error) {
|
||||
// Always use the JSON log as the source
|
||||
return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf, false)
|
||||
}
|
||||
|
||||
// mirrorMQSCLogs starts a goroutine to mirror the contents of the auto-config mqsc logs
|
||||
func mirrorMQSCLogs(ctx context.Context, wg *sync.WaitGroup, name string, mf mirrorFunc) (chan error, error) {
|
||||
qm, err := mqini.GetQueueManager(name)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "autocfgmqsc.LOG")
|
||||
return mirrorLog(ctx, wg, f, true, mf, false)
|
||||
}
|
||||
|
||||
// mirrorQueueManagerErrorLogs starts a goroutine to mirror the contents of the MQ queue manager error logs
|
||||
func mirrorQueueManagerErrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||
// Always use the JSON log as the source
|
||||
qm, err := mqini.GetQueueManager(name)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return nil, err
|
||||
}
|
||||
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
|
||||
return mirrorLog(ctx, wg, f, fromStart, mf, true)
|
||||
}
|
||||
|
||||
// mirrorMQSimpleAuthLogs starts a goroutine to mirror the contents of the MQ SimpleAuth authorization service's log
|
||||
func mirrorMQSimpleAuthLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||
return mirrorLog(ctx, wg, "/var/mqm/errors/simpleauth.json", false, mf, true)
|
||||
}
|
||||
|
||||
// mirrorWebServerLogs starts a goroutine to mirror the contents of the Liberty web server messages.log
|
||||
func mirrorWebServerLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||
return mirrorLog(ctx, wg, "/var/mqm/web/installations/Installation1/servers/mqweb/logs/messages.log", fromStart, mf, true)
|
||||
}
|
||||
|
||||
func getDebug() bool {
|
||||
debug := os.Getenv("DEBUG")
|
||||
if debug == "true" || debug == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func configureLogger(name string) (mirrorFunc, error) {
|
||||
var err error
|
||||
f := getLogFormat()
|
||||
d := getDebug()
|
||||
switch f {
|
||||
case "json":
|
||||
log, err = logger.NewLogger(os.Stderr, d, true, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(msg string, isQMLog bool) bool {
|
||||
arrLoggingConsoleExcludeIds := strings.Split(strings.ToUpper(os.Getenv("MQ_LOGGING_CONSOLE_EXCLUDE_ID")), ",")
|
||||
if isExcludedMsgIdPresent(msg, arrLoggingConsoleExcludeIds) {
|
||||
//If excluded id is present do not mirror it, return back
|
||||
return false
|
||||
}
|
||||
// Check if the message is JSON
|
||||
if len(msg) > 0 && msg[0] == '{' {
|
||||
obj, err := processLogMessage(msg)
|
||||
if err == nil && isQMLog && filterQMLogMessage(obj) {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshall JSON in log message - %v", msg)
|
||||
} else {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
} else {
|
||||
// The log being mirrored isn't JSON. This can happen only in case of 'mqsc' logs
|
||||
// Also if the logging source is from autocfgmqsc.LOG, then we have to construct the json string as per below logic
|
||||
if checkLogSourceForMirroring("mqsc") && canMQSCLogBeMirroredToConsole(msg) {
|
||||
logLevel := determineMQSCLogLevel(strings.TrimSpace(msg))
|
||||
fmt.Printf("{\"ibm_datetime\":\"%s\",\"type\":\"mqsc_log\",\"loglevel\":\"%s\",\"message\":\"%s\"}\n",
|
||||
getTimeStamp(), logLevel, strings.TrimSpace(msg))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, nil
|
||||
case "basic":
|
||||
log, err = logger.NewLogger(os.Stderr, d, false, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(msg string, isQMLog bool) bool {
|
||||
arrLoggingConsoleExcludeIds := strings.Split(strings.ToUpper(os.Getenv("MQ_LOGGING_CONSOLE_EXCLUDE_ID")), ",")
|
||||
if isExcludedMsgIdPresent(msg, arrLoggingConsoleExcludeIds) {
|
||||
//If excluded id is present do not mirror it, return back
|
||||
return false
|
||||
}
|
||||
// Check if the message is JSON
|
||||
if len(msg) > 0 && msg[0] == '{' {
|
||||
// Parse the JSON message, and print a simplified version
|
||||
obj, err := processLogMessage(msg)
|
||||
if err == nil && isQMLog && filterQMLogMessage(obj) {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshall JSON in log message - %v", err)
|
||||
} else {
|
||||
fmt.Print(formatBasic(obj))
|
||||
}
|
||||
} else {
|
||||
// The log being mirrored isn't JSON, so just print it. This can happen only in case of mqsc logs
|
||||
if checkLogSourceForMirroring("mqsc") && canMQSCLogBeMirroredToConsole(msg) {
|
||||
log.Printf(strings.TrimSpace(msg))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, nil
|
||||
default:
|
||||
log, err = logger.NewLogger(os.Stdout, d, false, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||
}
|
||||
}
|
||||
|
||||
func processLogMessage(msg string) (map[string]interface{}, error) {
|
||||
var obj map[string]interface{}
|
||||
err := json.Unmarshal([]byte(msg), &obj)
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func filterQMLogMessage(obj map[string]interface{}) bool {
|
||||
hostname, err := os.Hostname()
|
||||
if os.Getenv("MQ_MULTI_INSTANCE") == "true" && err == nil && !strings.Contains(obj["host"].(string), hostname) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Function to check if ids provided in MQ_LOGGING_CONSOLE_EXCLUDE_ID are present in given log line or not
|
||||
func isExcludedMsgIdPresent(msg string, envExcludeIds []string) bool {
|
||||
for _, id := range envExcludeIds {
|
||||
if id != "" && strings.Contains(msg, strings.TrimSpace(id)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func logDiagnostics() {
|
||||
if getDebug() {
|
||||
log.Debug("--- Start Diagnostics ---")
|
||||
|
||||
// show the directory ownership/permissions
|
||||
// #nosec G104
|
||||
out, _, _ := command.Run("ls", "-l", "/mnt/")
|
||||
log.Debugf("/mnt/:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm")
|
||||
log.Debugf("/mnt/mqm:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm/data")
|
||||
log.Debugf("/mnt/mqm/data:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-log/log")
|
||||
log.Debugf("/mnt/mqm-log/log:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-data/qmgrs")
|
||||
log.Debugf("/mnt/mqm-data/qmgrs:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/var/mqm")
|
||||
log.Debugf("/var/mqm:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/var/mqm/errors")
|
||||
log.Debugf("/var/mqm/errors:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/etc/mqm")
|
||||
log.Debugf("/etc/mqm:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/run")
|
||||
log.Debugf("/run:\n%s", out)
|
||||
|
||||
// Print out summary of any FDCs
|
||||
// #nosec G204
|
||||
cmd := exec.Command("/opt/mqm/bin/ffstsummary")
|
||||
cmd.Dir = "/var/mqm/errors"
|
||||
// #nosec G104
|
||||
outB, _ := cmd.CombinedOutput()
|
||||
log.Debugf("ffstsummary:\n%s", string(outB))
|
||||
|
||||
log.Debug("--- End Diagnostics ---")
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the value of MQ_LOGGING_CONSOLE_SOURCE environment variable
|
||||
func getMQLogConsoleSource() string {
|
||||
return strings.ToLower(strings.TrimSpace(os.Getenv("MQ_LOGGING_CONSOLE_SOURCE")))
|
||||
|
||||
}
|
||||
|
||||
// Function to check if valid values are provided for environment variable MQ_LOGGING_CONSOLE_SOURCE. If not valid, main program throws a warning to console
|
||||
func isLogConsoleSourceValid() bool {
|
||||
mqLogSource := getMQLogConsoleSource()
|
||||
retValue := false
|
||||
//If nothing is set, we will mirror all, so valid
|
||||
if mqLogSource == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
logConsoleSource := strings.Split(mqLogSource, ",")
|
||||
//This will find out if the environment variable contains permitted values and is comma separated
|
||||
for _, src := range logConsoleSource {
|
||||
switch strings.TrimSpace(src) {
|
||||
//If it is a permitted value, it is valid. Keep it as true, but dont return it. We may encounter something junk soon
|
||||
case "qmgr", "web", "mqsc", "":
|
||||
retValue = true
|
||||
//If invalid entry arrives in-between/anywhere, just return false, there is no turning back
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return retValue
|
||||
}
|
||||
|
||||
// To check which all logs have to be mirrored
|
||||
func checkLogSourceForMirroring(source string) bool {
|
||||
logsrcs := getMQLogConsoleSource()
|
||||
|
||||
//Nothing set, this is when we mirror both qmgr & web
|
||||
if logsrcs == "" {
|
||||
if source == "qmgr" || source == "web" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Split the csv environment value so that we get an accurate comparison instead of a contains() check
|
||||
logSrcArr := strings.Split(logsrcs, ",")
|
||||
|
||||
//Iterate through the array to decide on mirroring
|
||||
for _, arr := range logSrcArr {
|
||||
switch strings.TrimSpace(arr) {
|
||||
case "qmgr":
|
||||
//If value of source is qmgr and it exists in environment variable, mirror qmgr logs
|
||||
if source == "qmgr" {
|
||||
return true
|
||||
}
|
||||
case "web":
|
||||
//If value of source is web and it exists in environment variable, and mirror web logs
|
||||
if source == "web" {
|
||||
//If older environment variable is set make sure to print appropriate message
|
||||
if os.Getenv("MQ_ENABLE_EMBEDDED_WEB_SERVER_LOG") != "" {
|
||||
log.Println("Environment variable MQ_ENABLE_EMBEDDED_WEB_SERVER_LOG has now been replaced. Use MQ_LOGGING_CONSOLE_SOURCE instead.")
|
||||
}
|
||||
return true
|
||||
}
|
||||
case "mqsc":
|
||||
//If value of input parameter is mqsc and it exists in environment variable, mirror mqsc logs
|
||||
if source == "mqsc" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// canMQSCLogBeMirroredToConsole does the following check. The autocfgmqsc.log may contain comments, empty or unformatted lines. These will be ignored
|
||||
func canMQSCLogBeMirroredToConsole(message string) bool {
|
||||
if len(message) > 0 && !strings.HasPrefix(strings.TrimSpace(message), ": *") && !(strings.TrimSpace(message) == ":") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getTimeStamp fetches current time stamp
|
||||
func getTimeStamp() string {
|
||||
const timestampFormat string = "2008-01-02T15:04:05.000Z07:00"
|
||||
t := time.Now()
|
||||
return t.Format(timestampFormat)
|
||||
}
|
||||
|
||||
// determineMQSCLogLevel finds out log level based on if the message contains 'AMQxxxxE:' string or not.
|
||||
func determineMQSCLogLevel(message string) string {
|
||||
//Match the below pattern
|
||||
re := regexp.MustCompile(`AMQ[0-9]+E:`)
|
||||
result := re.FindStringSubmatch(message)
|
||||
|
||||
if len(result) > 0 {
|
||||
return "ERROR"
|
||||
} else {
|
||||
return "INFO"
|
||||
}
|
||||
}
|
||||
149
cmd/runmqserver/logging_test.go
Normal file
149
cmd/runmqserver/logging_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2020, 2024
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var formatBasicTests = []struct {
|
||||
in []byte
|
||||
outContains string
|
||||
}{
|
||||
{
|
||||
[]byte("{\"ibm_datetime\":\"2020/06/24 00:00:00\",\"message\":\"Hello world\"}"),
|
||||
"Hello",
|
||||
},
|
||||
{
|
||||
[]byte("{\"ibm_datetime\":\"2020/06/24 00:00:00\",\"message\":\"Hello world\", \"ibm_commentInsert1\":\"foo\"}"),
|
||||
"CommentInsert1(foo)",
|
||||
},
|
||||
{
|
||||
[]byte("{\"ibm_datetime\":\"2020/06/24 00:00:00\",\"message\":\"Hello world\", \"ibm_arithInsert1\":1}"),
|
||||
"ArithInsert1(1)",
|
||||
},
|
||||
}
|
||||
|
||||
func TestFormatBasic(t *testing.T) {
|
||||
for i, table := range formatBasicTests {
|
||||
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
|
||||
var inObj map[string]interface{}
|
||||
json.Unmarshal(table.in, &inObj)
|
||||
t.Logf("Unmarshalled: %+v", inObj)
|
||||
out := formatBasic(inObj)
|
||||
if !strings.Contains(out, table.outContains) {
|
||||
t.Errorf("formatBasic() with input=%v - expected output to contain %v, got %v", string(table.in), table.outContains, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This test covers for functions isLogConsoleSourceValid() & checkLogSourceForMirroring()
|
||||
var mqLogSourcesTests = []struct {
|
||||
testNum int
|
||||
logsrc string
|
||||
exptValid bool
|
||||
exptQmgrSrc bool
|
||||
exptWebSrc bool
|
||||
exptMqscSrc bool
|
||||
}{
|
||||
{1, "qmgr,web", true, true, true, false},
|
||||
{2, "qmgr", true, true, false, false},
|
||||
{3, "web,qmgr", true, true, true, false},
|
||||
{4, "web", true, false, true, false},
|
||||
{5, " ", true, true, true, false},
|
||||
{6, "QMGR,WEB", true, true, true, false},
|
||||
{7, "qmgr, ", true, true, false, false},
|
||||
{8, "qmgr , web", true, true, true, false},
|
||||
{9, "qmgr,dummy", false, true, false, false},
|
||||
{10, "fake,dummy", false, false, false, false},
|
||||
{11, "qmgr,fake,dummy", false, true, false, false},
|
||||
{12, "fake,dummy,web", false, false, true, false},
|
||||
{13, "true", false, false, false, false},
|
||||
{14, "false", false, false, false, false},
|
||||
{15, "", true, true, true, false},
|
||||
{16, "mqsc", true, false, false, true},
|
||||
{17, "MQSC", true, false, false, true},
|
||||
{18, "qmgr,mqsc", true, true, false, true},
|
||||
{19, "web,mqsc", true, false, true, true},
|
||||
{20, "qmgr,web,mqsc", true, true, true, true},
|
||||
}
|
||||
|
||||
func TestLoggingConsoleSourceInputs(t *testing.T) {
|
||||
for _, mqlogsrctest := range mqLogSourcesTests {
|
||||
err := os.Setenv("MQ_LOGGING_CONSOLE_SOURCE", mqlogsrctest.logsrc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
isValid := isLogConsoleSourceValid()
|
||||
if isValid != mqlogsrctest.exptValid {
|
||||
t.Errorf("Expected return value from isLogConsoleSourceValid() is %v for MQ_LOGGING_CONSOLE_SOURCE='%v', got %v\n", mqlogsrctest.exptValid, mqlogsrctest.logsrc, isValid)
|
||||
}
|
||||
isLogSrcQmgr := checkLogSourceForMirroring("qmgr")
|
||||
if isLogSrcQmgr != mqlogsrctest.exptQmgrSrc {
|
||||
t.Errorf("Expected return value from checkLogSourceForMirroring() is %v for MQ_LOGGING_CONSOLE_SOURCE='%v', got %v\n", mqlogsrctest.exptQmgrSrc, mqlogsrctest.logsrc, isLogSrcQmgr)
|
||||
}
|
||||
isLogSrcWeb := checkLogSourceForMirroring("web")
|
||||
if isLogSrcWeb != mqlogsrctest.exptWebSrc {
|
||||
t.Errorf("Expected return value from checkLogSourceForMirroring() is %v for MQ_LOGGING_CONSOLE_SOURCE='%v', got %v\n", mqlogsrctest.exptWebSrc, mqlogsrctest.logsrc, isLogSrcWeb)
|
||||
}
|
||||
isLogSrcMqsc := checkLogSourceForMirroring("mqsc")
|
||||
if isLogSrcMqsc != mqlogsrctest.exptMqscSrc {
|
||||
t.Errorf("Expected return value from checkLogSourceForMirroring() is %v for MQ_LOGGING_CONSOLE_SOURCE='%v', got %v\n", mqlogsrctest.exptMqscSrc, mqlogsrctest.logsrc, isLogSrcMqsc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test covers for function isExcludedMsgIdPresent()
|
||||
var mqExcludeIDTests = []struct {
|
||||
testNum int
|
||||
exculdeIDsArr []string
|
||||
expectedRetVal bool
|
||||
logEntry string
|
||||
}{
|
||||
{
|
||||
1,
|
||||
[]string{"AMQ5051I", "AMQ5037I", "AMQ5975I"},
|
||||
true,
|
||||
"{\"ibm_messageId\":\"AMQ5051I\",\"ibm_arithInsert1\":0,\"ibm_arithInsert2\":1,\"message\":\"AMQ5051I: The queue manager task 'AUTOCONFIG' has started.\"}",
|
||||
},
|
||||
{
|
||||
2,
|
||||
[]string{"AMQ5975I", "AMQ5037I"},
|
||||
false,
|
||||
"{\"ibm_messageId\":\"AMQ5051I\",\"ibm_arithInsert1\":0,\"ibm_arithInsert2\":1,\"message\":\"AMQ5051I: The queue manager task 'AUTOCONFIG' has started.\"}",
|
||||
},
|
||||
{
|
||||
3,
|
||||
[]string{""},
|
||||
false,
|
||||
"{\"ibm_messageId\":\"AMQ5051I\",\"ibm_arithInsert1\":0,\"ibm_arithInsert2\":1,\"message\":\"AMQ5051I: The queue manager task 'AUTOCONFIG' has started.\"}",
|
||||
},
|
||||
}
|
||||
|
||||
func TestIsExcludedMsgIDPresent(t *testing.T) {
|
||||
for _, excludeIDTest := range mqExcludeIDTests {
|
||||
retVal := isExcludedMsgIdPresent(excludeIDTest.logEntry, excludeIDTest.exculdeIDsArr)
|
||||
if retVal != excludeIDTest.expectedRetVal {
|
||||
t.Errorf("%v. Expected return value from isExcludedMsgIdPresent() is %v for MQ_LOGGING_CONSOLE_EXCLUDE_ID='%v', got %v\n",
|
||||
excludeIDTest.testNum, excludeIDTest.expectedRetVal, excludeIDTest.exculdeIDsArr, retVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
432
cmd/runmqserver/main.go
Normal file
432
cmd/runmqserver/main.go
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/copy"
|
||||
"github.com/ibm-messaging/mq-container/internal/fips"
|
||||
"github.com/ibm-messaging/mq-container/internal/ha"
|
||||
"github.com/ibm-messaging/mq-container/internal/metrics"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
"github.com/ibm-messaging/mq-container/internal/simpleauth"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
"github.com/ibm-messaging/mq-container/pkg/containerruntimelogger"
|
||||
"github.com/ibm-messaging/mq-container/pkg/name"
|
||||
)
|
||||
|
||||
func doMain() error {
|
||||
var initFlag = flag.Bool("i", false, "initialize volume only, then exit")
|
||||
var infoFlag = flag.Bool("info", false, "Display debug info, then exit")
|
||||
var noLogRuntimeFlag = flag.Bool("nologruntime", false, "used when running this program from another program, to control log output")
|
||||
var devFlag = flag.Bool("dev", false, "used when running this program from runmqdevserver to control how TLS is configured")
|
||||
flag.Parse()
|
||||
|
||||
name, nameErr := name.GetQueueManagerName()
|
||||
mf, err := configureLogger(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check whether they only want debug info
|
||||
if *infoFlag {
|
||||
logVersionInfo()
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
log.Printf("Error displaying container details: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = verifySingleProcess()
|
||||
if err != nil {
|
||||
// We don't do the normal termination here as it would create a termination file.
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if nameErr != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = ready.Clear()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
accepted, err := checkLicense()
|
||||
if err != nil {
|
||||
logTerminationf("Error checking license acceptance: %v", err)
|
||||
return err
|
||||
}
|
||||
if !accepted {
|
||||
err = errors.New("License not accepted")
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Using queue manager name: %v", name)
|
||||
|
||||
// Create a startup context to be used by the signalHandler to ensure the final reap of zombie processes only occurs after all startup processes are spawned
|
||||
startupCtx, markStartupComplete := context.WithCancel(context.Background())
|
||||
var startupMarkedComplete bool
|
||||
// If the main thread returns before completing startup, cancel the startup context to unblock the signalHandler
|
||||
defer func() {
|
||||
if !startupMarkedComplete {
|
||||
markStartupComplete()
|
||||
}
|
||||
}()
|
||||
// Start signal handler
|
||||
signalControl := signalHandler(name, startupCtx)
|
||||
// Enable diagnostic collecting on failure
|
||||
collectDiagOnFail = true
|
||||
|
||||
if *noLogRuntimeFlag == false {
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = createVolume("/mnt/mqm/data")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = createVolume("/mnt/mqm-log/log")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = createVolume("/mnt/mqm-data/qmgrs")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete contents of /run/scratch directory.
|
||||
err = cleanVolume("/run/scratch")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete contents of /run/mqm directory.
|
||||
err = cleanVolume("/run/mqm")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create ephemeral volumes
|
||||
err = createVolume("/run/scratch/runmqserver")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Queue manager i.e crtmqm command creates socket and
|
||||
// others files in /run/mqm directory.
|
||||
err = createVolume("/run/mqm")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialise 15-tls.mqsc file on ephemeral volume
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err = os.WriteFile("/run/15-tls.mqsc", []byte(""), 0660)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialise native-ha ini files file on ephemeral volume
|
||||
nativeHAINIs := []string{
|
||||
"10-native-ha.ini",
|
||||
"10-native-ha-instance.ini",
|
||||
"10-native-ha-keystore.ini",
|
||||
}
|
||||
for _, iniFile := range nativeHAINIs {
|
||||
// #nosec G306 - its a read by owner/s group, and pose no harm.
|
||||
err = os.WriteFile(path.Join("/run", iniFile), []byte(""), 0660)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy default mqwebcontainer.xml file to ephemeral volume
|
||||
if *devFlag && os.Getenv("MQ_DEV") == "true" {
|
||||
err = copy.CopyFile("/etc/mqm/web/installations/Installation1/servers/mqweb/mqwebcontainer.xml.dev", "/run/mqwebcontainer.xml")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = copy.CopyFile("/etc/mqm/web/installations/Installation1/servers/mqweb/mqwebcontainer.xml.default", "/run/mqwebcontainer.xml")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy default tls.xml file to ephemeral volume
|
||||
err = copy.CopyFile("/etc/mqm/web/installations/Installation1/servers/mqweb/tls.xml.default", "/run/tls.xml")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy default jvm.options file to ephemeral volume
|
||||
err = copy.CopyFile("/etc/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults/jvm.options.default", "/run/jvm.options")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
enableTraceCrtmqdir := os.Getenv("MQ_ENABLE_TRACE_CRTMQDIR")
|
||||
if enableTraceCrtmqdir == "true" || enableTraceCrtmqdir == "1" {
|
||||
err = startMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = createDirStructure()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if enableTraceCrtmqdir == "true" || enableTraceCrtmqdir == "1" {
|
||||
err = endMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If init flag is set, exit now
|
||||
if *initFlag {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print out versioning information
|
||||
logVersionInfo()
|
||||
|
||||
// Determine FIPS compliance level
|
||||
fips.ProcessFIPSType(log)
|
||||
|
||||
keyLabel, defaultCmsKeystore, defaultP12Truststore, err := tls.ConfigureDefaultTLSKeystores()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = tls.ConfigureTLS(keyLabel, defaultCmsKeystore, *devFlag, log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
//Validate MQ_LOG_CONSOLE_SOURCE variable
|
||||
if !isLogConsoleSourceValid() {
|
||||
log.Println("One or more invalid value is provided for MQ_LOGGING_CONSOLE_SOURCE. Allowed values are 'qmgr','web' and 'mqsc' in csv format")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
log.Debug("Waiting for log mirroring to complete")
|
||||
wg.Wait()
|
||||
}()
|
||||
ctx, cancelMirror := context.WithCancel(context.Background())
|
||||
defer func() {
|
||||
log.Debug("Cancel log mirroring")
|
||||
cancelMirror()
|
||||
}()
|
||||
|
||||
//For mirroring web server logs if source variable is set
|
||||
if checkLogSourceForMirroring("web") {
|
||||
// Always log from the end of the web server messages.log, because the log rotation should happen as soon as the web server starts
|
||||
_, err = mirrorWebServerLogs(ctx, &wg, name, false, mf)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = postInit(name, keyLabel, defaultP12Truststore)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if os.Getenv("MQ_NATIVE_HA") == "true" {
|
||||
err = ha.ConfigureNativeHA(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Post FIPS initialization processing
|
||||
fips.PostInit(log)
|
||||
|
||||
enableTraceCrtmqm := os.Getenv("MQ_ENABLE_TRACE_CRTMQM")
|
||||
if enableTraceCrtmqm == "true" || enableTraceCrtmqm == "1" {
|
||||
err = startMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
newQM, err := createQueueManager(name, *devFlag)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if enableTraceCrtmqm == "true" || enableTraceCrtmqm == "1" {
|
||||
err = endMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//For mirroring mq system logs and qm logs, if environment variable is set
|
||||
if checkLogSourceForMirroring("qmgr") {
|
||||
//Mirror MQ system logs
|
||||
_, err = mirrorSystemErrorLogs(ctx, &wg, mf)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
//Mirror queue manager logs
|
||||
_, err = mirrorQueueManagerErrorLogs(ctx, &wg, name, newQM, mf)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if *devFlag && simpleauth.IsEnabled() {
|
||||
_, err = mirrorMQSimpleAuthLogs(ctx, &wg, name, newQM, mf)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = updateCommandLevel()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
enableTraceStrmqm := os.Getenv("MQ_ENABLE_TRACE_STRMQM")
|
||||
if enableTraceStrmqm == "true" || enableTraceStrmqm == "1" {
|
||||
err = startMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// This is a developer image only change
|
||||
// This workaround should be removed and handled via <crtmqm -ii>, when inimerge is ready to handle stanza ordering
|
||||
if *devFlag && simpleauth.IsEnabled() {
|
||||
err = updateQMini(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = startQueueManager(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
//If the queue manager has started successfully, reflect mqsc logs when enabled
|
||||
if checkLogSourceForMirroring("mqsc") {
|
||||
_, err = mirrorMQSCLogs(ctx, &wg, name, mf)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if enableTraceStrmqm == "true" || enableTraceStrmqm == "1" {
|
||||
err = endMQTrace()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
enableMetrics := os.Getenv("MQ_ENABLE_METRICS")
|
||||
if enableMetrics == "true" || enableMetrics == "1" {
|
||||
go metrics.GatherMetrics(name, log)
|
||||
} else {
|
||||
log.Println("Metrics are disabled")
|
||||
}
|
||||
|
||||
// Start reaping zombies from now on.
|
||||
// Start this here, so that we don't reap any sub-processes created
|
||||
// by this process (e.g. for crtmqm or strmqm)
|
||||
signalControl <- startReaping
|
||||
// Reap zombies now, just in case we've already got some
|
||||
signalControl <- reapNow
|
||||
|
||||
startupMarkedComplete = true
|
||||
markStartupComplete()
|
||||
|
||||
// Write a file to indicate that chkmqready should now work as normal
|
||||
err = ready.Set()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
// Wait for terminate signal
|
||||
<-signalControl
|
||||
return nil
|
||||
}
|
||||
|
||||
var osExit = os.Exit
|
||||
|
||||
func main() {
|
||||
err := doMain()
|
||||
if err != nil {
|
||||
osExit(1)
|
||||
}
|
||||
}
|
||||
60
cmd/runmqserver/main_test.go
Normal file
60
cmd/runmqserver/main_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2023
|
||||
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/pkg/logger"
|
||||
)
|
||||
|
||||
var test *bool
|
||||
|
||||
func init() {
|
||||
test = flag.Bool("test", false, "Set to true when running tests for coverage")
|
||||
log, _ = logger.NewLogger(os.Stdout, true, false, "test")
|
||||
}
|
||||
|
||||
// Test started when the test binary is started. Only calls main.
|
||||
func TestSystem(t *testing.T) {
|
||||
if *test {
|
||||
var oldExit = osExit
|
||||
defer func() {
|
||||
osExit = oldExit
|
||||
}()
|
||||
|
||||
filename, ok := os.LookupEnv("EXIT_CODE_FILE")
|
||||
if !ok {
|
||||
filename = "/var/coverage/exitCode"
|
||||
} else {
|
||||
filename = filepath.Join("/var/coverage/", filename)
|
||||
}
|
||||
|
||||
osExit = func(rc int) {
|
||||
// Write the exit code to a file instead
|
||||
log.Printf("Writing exit code %v to file %v", strconv.Itoa(rc), filename)
|
||||
err := os.WriteFile(filename, []byte(strconv.Itoa(rc)), 0644)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
main()
|
||||
}
|
||||
}
|
||||
200
cmd/runmqserver/mirror.go
Normal file
200
cmd/runmqserver/mirror.go
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// waitForFile waits until the specified file exists
|
||||
func waitForFile(ctx context.Context, path string) (os.FileInfo, error) {
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
// Wait for file to exist
|
||||
for {
|
||||
select {
|
||||
// Check to see if cancellation has been requested
|
||||
case <-ctx.Done():
|
||||
return os.Stat(path)
|
||||
default:
|
||||
fi, err = os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("mirror: unable to get info on file %v", path)
|
||||
}
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mirrorFunc func(msg string, isQMLog bool) bool
|
||||
|
||||
// mirrorAvailableMessages prints lines from the file, until no more are available
|
||||
func mirrorAvailableMessages(f *os.File, mf mirrorFunc, isQMLog bool) {
|
||||
scanner := bufio.NewScanner(f)
|
||||
count := 0
|
||||
for scanner.Scan() {
|
||||
t := scanner.Text()
|
||||
if mf(t, isQMLog) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
log.Debugf("Mirrored %v log entries from %v", count, f.Name())
|
||||
}
|
||||
err := scanner.Err()
|
||||
if err != nil {
|
||||
log.Errorf("Error reading file %v: %v", f.Name(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// mirrorLog tails the specified file, and logs each line to stdout.
|
||||
// This is useful for usability, as the container console log can show
|
||||
// messages from the MQ error logs.
|
||||
func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc, isQMLog bool) (chan error, error) {
|
||||
errorChannel := make(chan error, 1)
|
||||
var offset int64 = -1
|
||||
var f *os.File
|
||||
var err error
|
||||
var fi os.FileInfo
|
||||
// Need to check if the file exists before returning, otherwise we have a
|
||||
// race to see if the new file get created before we can test for it
|
||||
fi, err = os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// File doesn't exist, so ensure we start at the beginning
|
||||
offset = 0
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// If the file exists, open it now, before we return. This makes sure
|
||||
// the file is open before the queue manager is created or started.
|
||||
// Otherwise, there would be the potential for a nearly-full file to
|
||||
// rotate before the goroutine had a chance to open it.
|
||||
// #nosec G304 - no harm, we open readonly and check error.
|
||||
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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() {
|
||||
// Notify the wait group when this goroutine ends
|
||||
defer func() {
|
||||
log.Debugf("Finished monitoring %v", path)
|
||||
wg.Done()
|
||||
}()
|
||||
if f == nil {
|
||||
// File didn't exist, so need to wait for it
|
||||
fi, err = waitForFile(ctx, path)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
if fi == nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("File exists: %v, %v", path, fi.Size())
|
||||
// #nosec G304 - no harm, we open readonly and check error.
|
||||
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fi, err = f.Stat()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
// The file now exists. If it didn't exist before we started, offset=0
|
||||
// Always start at the beginning if we've been told to go from the start
|
||||
if offset != 0 && !fromStart {
|
||||
log.Debugf("Seeking offset %v in file %v", offset, path)
|
||||
_, err = f.Seek(offset, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to return to offset %v: %v", offset, err)
|
||||
}
|
||||
}
|
||||
closing := false
|
||||
for {
|
||||
// If there's already data there, mirror it now.
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
// Wait for the new log file (after rotation)
|
||||
newFI, err := waitForFile(ctx, path)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
if !os.SameFile(fi, newFI) {
|
||||
log.Debugf("Detected log rotation in file %v", path)
|
||||
// WARNING: There is a possible race condition here. If *another*
|
||||
// log rotation happens before we can open the new file, then we
|
||||
// could skip all those messages. This could happen with a very small
|
||||
// MQ error log size.
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to close mirror file handle: %v", err)
|
||||
}
|
||||
// Re-open file
|
||||
log.Debugf("Re-opening error log file %v", path)
|
||||
// #nosec G304 - no harm, we open readonly and check error.
|
||||
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
fi = newFI
|
||||
// Don't seek this time, because we know it's a new file
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debugf("Context cancelled for mirroring %v", path)
|
||||
if closing {
|
||||
log.Debugf("Shutting down mirror for %v", path)
|
||||
return
|
||||
}
|
||||
// Set a flag, to allow one more time through the loop
|
||||
closing = true
|
||||
default:
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return errorChannel, nil
|
||||
}
|
||||
194
cmd/runmqserver/mirror_test.go
Normal file
194
cmd/runmqserver/mirror_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMirrorLogWithoutRotation(t *testing.T) {
|
||||
// Repeat the test multiple times, to help identify timing problems
|
||||
for i := 0; i < 10; i++ {
|
||||
t.Run(t.Name()+strconv.Itoa(i), func(t *testing.T) {
|
||||
// Use just the sub-test name in the file name
|
||||
tmp, err := os.CreateTemp("", strings.Split(t.Name(), "/")[1])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(tmp.Name())
|
||||
defer os.Remove(tmp.Name())
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Logging 3 JSON messages")
|
||||
fmt.Fprintln(f, "{\"message\"=\"A\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||
f.Close()
|
||||
cancel()
|
||||
wg.Wait()
|
||||
if count != 3 {
|
||||
t.Fatalf("Expected 3 log entries; got %v", count)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMirrorLogWithRotation(t *testing.T) {
|
||||
// Repeat the test multiple times, to help identify timing problems
|
||||
for i := 0; i < 5; i++ {
|
||||
t.Run(t.Name()+strconv.Itoa(i), func(t *testing.T) {
|
||||
// Use just the sub-test name in the file name
|
||||
tmp, err := os.CreateTemp("", strings.Split(t.Name(), "/")[1])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(tmp.Name())
|
||||
defer func() {
|
||||
t.Log("Removing file")
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("Logging 3 JSON messages")
|
||||
fmt.Fprintln(f, "{\"message\"=\"A\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||
f.Close()
|
||||
|
||||
// Rotate the file, by renaming it
|
||||
rotated := tmp.Name() + ".1"
|
||||
os.Rename(tmp.Name(), rotated)
|
||||
defer os.Remove(rotated)
|
||||
// Open a new file, with the same name as before
|
||||
f, err = os.OpenFile(tmp.Name(), os.O_WRONLY|os.O_CREATE, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("Logging 2 more JSON messages")
|
||||
fmt.Fprintln(f, "{\"message\"=\"D\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"E\"}")
|
||||
f.Close()
|
||||
|
||||
// Shut the mirroring down
|
||||
cancel()
|
||||
wg.Wait()
|
||||
|
||||
if count != 5 {
|
||||
t.Fatalf("Expected 5 log entries; got %v", count)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMirrorLogExistingFile(t *testing.T, newQM bool) int {
|
||||
tmp, err := os.CreateTemp("", t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(tmp.Name())
|
||||
log.Println("Logging 1 message before we start")
|
||||
os.WriteFile(tmp.Name(), []byte("{\"message\"=\"A\"}\n"), 0600)
|
||||
defer os.Remove(tmp.Name())
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := os.OpenFile(tmp.Name(), os.O_APPEND|os.O_WRONLY, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Println("Logging 2 new JSON messages")
|
||||
fmt.Fprintln(f, "{\"message\"=\"B\"}")
|
||||
fmt.Fprintln(f, "{\"message\"=\"C\"}")
|
||||
f.Close()
|
||||
cancel()
|
||||
wg.Wait()
|
||||
return count
|
||||
}
|
||||
|
||||
// TestMirrorLogExistingFile tests that we only get new log messages, if the
|
||||
// log file already exists
|
||||
func TestMirrorLogExistingFile(t *testing.T) {
|
||||
count := testMirrorLogExistingFile(t, false)
|
||||
if count != 2 {
|
||||
t.Fatalf("Expected 2 log entries; got %v", count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMirrorLogExistingFileButNewQueueManager tests that we only get all log
|
||||
// messages, even if the file exists, if we tell it we want all messages
|
||||
func TestMirrorLogExistingFileButNewQueueManager(t *testing.T) {
|
||||
count := testMirrorLogExistingFile(t, true)
|
||||
if count != 3 {
|
||||
t.Fatalf("Expected 3 log entries; got %v", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMirrorLogCancelWhileWaiting(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}()
|
||||
_, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string, isQMLog bool) bool {
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
cancel()
|
||||
wg.Wait()
|
||||
// No need to assert anything. If it didn't work, the code would have hung (TODO: not ideal)
|
||||
}
|
||||
59
cmd/runmqserver/post_init.go
Normal file
59
cmd/runmqserver/post_init.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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 (
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/fips"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
)
|
||||
|
||||
// postInit is run after /var/mqm is set up
|
||||
func postInit(name, keyLabel string, p12Truststore tls.KeyStoreData) error {
|
||||
enableWebServer := os.Getenv("MQ_ENABLE_EMBEDDED_WEB_SERVER")
|
||||
if enableWebServer == "true" || enableWebServer == "1" {
|
||||
|
||||
// Enable FIPS for MQ Web Server if asked for.
|
||||
if fips.IsFIPSEnabled() {
|
||||
err := configureFIPSWebServer(p12Truststore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the web server (if enabled)
|
||||
webKeystore, err := configureWebServer(keyLabel, p12Truststore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If trust-store is empty, set reference to point to the keystore
|
||||
webTruststoreRef := "MQWebTrustStore"
|
||||
if len(p12Truststore.TrustedCerts) == 0 {
|
||||
webTruststoreRef = "MQWebKeyStore"
|
||||
}
|
||||
|
||||
// Start the web server, in the background (if installed)
|
||||
// WARNING: No error handling or health checking available for the web server
|
||||
go func() {
|
||||
err = startWebServer(webKeystore, p12Truststore.Password, webTruststoreRef)
|
||||
if err != nil {
|
||||
log.Printf("Error starting web server: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
64
cmd/runmqserver/process.go
Normal file
64
cmd/runmqserver/process.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
// Verifies that we are the main or only instance of this program
|
||||
func verifySingleProcess() error {
|
||||
programName, err := determineExecutable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to determine name of this program - %v", err)
|
||||
}
|
||||
|
||||
// Verify that there is only one runmqserver
|
||||
_, err = verifyOnlyOne(programName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("You cannot run more than one instance of this program")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verifies that there is only one instance running of the given program name.
|
||||
func verifyOnlyOne(programName string) (int, error) {
|
||||
// #nosec G104
|
||||
out, _, _ := command.Run("ps", "-e", "--format", "cmd")
|
||||
//if this goes wrong then assume we are the only one
|
||||
numOfProg := strings.Count(out, programName)
|
||||
if numOfProg != 1 {
|
||||
return numOfProg, fmt.Errorf("Expected there to be only 1 instance of %s but found %d", programName, numOfProg)
|
||||
}
|
||||
return numOfProg, nil
|
||||
}
|
||||
|
||||
// Determines the name of the currently running executable.
|
||||
func determineExecutable() (string, error) {
|
||||
file, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, exec := filepath.Split(file)
|
||||
return exec, nil
|
||||
}
|
||||
360
cmd/runmqserver/qmgr.go
Normal file
360
cmd/runmqserver/qmgr.go
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2023
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
containerruntime "github.com/ibm-messaging/mq-container/internal/containerruntime"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqscredact"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqversion"
|
||||
"github.com/ibm-messaging/mq-container/internal/pathutils"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
)
|
||||
|
||||
// createDirStructure creates the default MQ directory structure under /var/mqm
|
||||
func createDirStructure() error {
|
||||
// log file diagnostics before and after crtmqdir if DEBUG=true
|
||||
logDiagnostics()
|
||||
out, rc, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-a")
|
||||
if err != nil {
|
||||
if rc == 10 {
|
||||
// Ignore warnings about 'mqwebuser.xml' being a symlink
|
||||
if !(strings.Join(strings.Fields(string(out)), " ") == "The filesystem object '/mnt/mqm/data/web/installations/Installation1/servers/mqweb/mqwebuser.xml' is a symbolic link.") {
|
||||
log.Printf("Warning creating directory structure: %v\n", string(out))
|
||||
}
|
||||
} else {
|
||||
log.Printf("Error creating directory structure: the 'crtmqdir' command returned with code: %v. Reason: %v\n", rc, string(out))
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Println("Created directory structure under /var/mqm")
|
||||
logDiagnostics()
|
||||
return nil
|
||||
}
|
||||
|
||||
// createQueueManager creates a queue manager, if it doesn't already exist.
|
||||
// It returns true if one was created (or a standby was created), or false if one already existed
|
||||
func createQueueManager(name string, devMode bool) (bool, error) {
|
||||
log.Printf("Creating queue manager %v", name)
|
||||
|
||||
mounts, err := containerruntime.GetMounts()
|
||||
if err != nil {
|
||||
log.Printf("Error getting mounts for queue manager")
|
||||
return false, err
|
||||
}
|
||||
|
||||
dataDir := getQueueManagerDataDir(mounts, replaceCharsInQMName(name))
|
||||
|
||||
// Run 'dspmqinf' to check if 'mqs.ini' configuration file exists
|
||||
// If command succeeds, the queue manager (or standby queue manager) has already been created
|
||||
_, _, err = command.Run("dspmqinf", name)
|
||||
if err == nil {
|
||||
log.Printf("Detected existing queue manager %v", name)
|
||||
// Check if MQ_QMGR_LOG_FILE_PAGES matches the value set in qm.ini
|
||||
lfp := os.Getenv("MQ_QMGR_LOG_FILE_PAGES")
|
||||
if lfp != "" {
|
||||
qmIniBytes, err := readQMIni(dataDir)
|
||||
if err != nil {
|
||||
log.Printf("Error reading qm.ini : %v", err)
|
||||
return false, err
|
||||
}
|
||||
if !validateLogFilePageSetting(qmIniBytes, lfp) {
|
||||
log.Println("Warning: the value of MQ_QMGR_LOG_FILE_PAGES does not match the value of 'LogFilePages' in the qm.ini. This setting cannot be altered after Queue Manager creation.")
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if 'qm.ini' configuration file exists for the queue manager
|
||||
// TODO : handle possible race condition - use a file lock?
|
||||
_, err = os.Stat(pathutils.CleanPath(dataDir, "qm.ini"))
|
||||
if err != nil {
|
||||
// If 'qm.ini' is not found - run 'crtmqm' to create a new queue manager
|
||||
args := getCreateQueueManagerArgs(mounts, name, devMode)
|
||||
out, rc, err := command.Run("crtmqm", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error creating queue manager: the 'crtmqm' command returned with code: %v. Reason: %v", rc, string(out))
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
// If 'qm.ini' is found - run 'addmqinf' to create a standby queue manager with existing configuration
|
||||
args := getCreateStandbyQueueManagerArgs(name)
|
||||
out, rc, err := command.Run("addmqinf", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error creating standby queue manager: the 'addmqinf' command returned with code: %v. Reason: %v",
|
||||
rc, string(out))
|
||||
return false, err
|
||||
}
|
||||
log.Println("Created standby queue manager")
|
||||
return true, nil
|
||||
}
|
||||
log.Println("Created queue manager")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// readQMIni reads the qm.ini file and returns it as a byte array
|
||||
// This function is specific to comply with the nosec.
|
||||
func readQMIni(dataDir string) ([]byte, error) {
|
||||
qmgrDir := pathutils.CleanPath(dataDir, "qm.ini")
|
||||
// #nosec G304 - qmgrDir filepath is derived from dspmqinf
|
||||
iniFileBytes, err := os.ReadFile(qmgrDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iniFileBytes, err
|
||||
}
|
||||
|
||||
// validateLogFilePageSetting validates if the specified logFilePage number is equal to the existing value in the qm.ini
|
||||
func validateLogFilePageSetting(iniFileBytes []byte, logFilePages string) bool {
|
||||
lfpString := "LogFilePages=" + logFilePages
|
||||
qminiConfigStr := string(iniFileBytes)
|
||||
return strings.Contains(qminiConfigStr, lfpString)
|
||||
}
|
||||
|
||||
func updateCommandLevel() error {
|
||||
level, ok := os.LookupEnv("MQ_CMDLEVEL")
|
||||
if ok && level != "" {
|
||||
log.Printf("Setting CMDLEVEL to %v", level)
|
||||
out, rc, err := command.Run("strmqm", "-e", "CMDLEVEL="+level)
|
||||
if err != nil {
|
||||
log.Printf("Error setting CMDLEVEL for queue manager: the 'strmqm' command returned with code: %v. Reason: %v",
|
||||
rc, string(out))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startQueueManager(name string) error {
|
||||
log.Println("Starting queue manager")
|
||||
out, rc, err := command.Run("strmqm", "-x", name)
|
||||
if err != nil {
|
||||
// 30=standby queue manager started, which is fine
|
||||
// 94=native HA replica started, which is fine
|
||||
if rc == 30 {
|
||||
log.Printf("Started standby queue manager")
|
||||
return nil
|
||||
} else if rc == 94 {
|
||||
log.Printf("Started replica queue manager")
|
||||
return nil
|
||||
}
|
||||
log.Printf("Error starting queue manager: the 'strmqm' command returned with code: %v. Reason: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
log.Println("Started queue manager")
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopQueueManager(name string) error {
|
||||
log.Println("Stopping queue manager")
|
||||
qmGracePeriod := os.Getenv("MQ_GRACE_PERIOD")
|
||||
status, err := ready.Status(context.Background(), name)
|
||||
if err != nil {
|
||||
log.Printf("Error getting status for queue manager %v. The 'dspmq' command returned reason: %v",
|
||||
name, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
isStandby := status.StandbyQM()
|
||||
args := []string{"-w", "-r", "-tp", qmGracePeriod, name}
|
||||
if os.Getenv("MQ_MULTI_INSTANCE") == "true" {
|
||||
if isStandby {
|
||||
args = []string{"-x", name}
|
||||
} else {
|
||||
args = []string{"-s", "-w", "-tp", qmGracePeriod, name}
|
||||
}
|
||||
}
|
||||
out, rc, err := command.Run("endmqm", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error stopping queue manager: the 'endmqm' command returned with code: %v. Reason: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
if isStandby {
|
||||
log.Printf("Stopped standby queue manager")
|
||||
} else {
|
||||
log.Println("Stopped queue manager")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startMQTrace() error {
|
||||
log.Println("Starting MQ trace")
|
||||
out, rc, err := command.Run("strmqtrc")
|
||||
if err != nil {
|
||||
log.Printf("Error starting MQ trace: the 'strmqtrc' command returned with code: %v. Reason: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
log.Println("Started MQ trace")
|
||||
return nil
|
||||
}
|
||||
|
||||
func endMQTrace() error {
|
||||
log.Println("Ending MQ Trace")
|
||||
out, rc, err := command.Run("endmqtrc")
|
||||
if err != nil {
|
||||
log.Printf("Error ending MQ trace: the 'endmqtrc' command returned with code: %v. Reason: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
log.Println("Ended MQ trace")
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatMQSCOutput(out string) string {
|
||||
// redact sensitive information
|
||||
out, _ = mqscredact.Redact(out)
|
||||
|
||||
// add tab characters to make it more readable as part of the log
|
||||
return strings.Replace(string(out), "\n", "\n\t", -1)
|
||||
}
|
||||
|
||||
func isStandbyQueueManager(name string) (bool, error) {
|
||||
out, rc, err := command.Run("dspmq", "-n", "-m", name)
|
||||
if err != nil {
|
||||
log.Printf("Error while getting status for queue manager %v: the 'dspmq' command returned with code: %v. Reason: %v",
|
||||
name, rc, string(out))
|
||||
return false, err
|
||||
}
|
||||
if strings.Contains(string(out), "(RUNNING AS STANDBY)") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getQueueManagerDataDir(mounts map[string]string, name string) string {
|
||||
dataDir := pathutils.CleanPath("/var/mqm/qmgrs", name)
|
||||
if _, ok := mounts["/mnt/mqm-data"]; ok {
|
||||
dataDir = pathutils.CleanPath("/mnt/mqm-data/qmgrs", name)
|
||||
}
|
||||
return dataDir
|
||||
}
|
||||
|
||||
func getCreateQueueManagerArgs(mounts map[string]string, name string, devMode bool) []string {
|
||||
|
||||
mqversionBase := "9.2.1.0"
|
||||
|
||||
// use "UserExternal" only if we are 9.2.1.0 or above.
|
||||
oaVal := "user"
|
||||
mqVersionCheck, err := mqversion.Compare(mqversionBase)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error comparing MQ versions for oa,rc: %v", mqVersionCheck)
|
||||
}
|
||||
if mqVersionCheck >= 0 {
|
||||
oaVal = "UserExternal"
|
||||
}
|
||||
|
||||
//build args
|
||||
args := []string{"-ii", "/etc/mqm/", "-ic", "/etc/mqm/", "-q", "-p", "1414"}
|
||||
|
||||
if os.Getenv("MQ_NATIVE_HA") == "true" {
|
||||
args = append(args, "-lr", os.Getenv("HOSTNAME"))
|
||||
}
|
||||
if devMode {
|
||||
args = append(args, "-oa", oaVal)
|
||||
}
|
||||
if _, ok := mounts["/mnt/mqm-log"]; ok {
|
||||
args = append(args, "-ld", "/mnt/mqm-log/log")
|
||||
}
|
||||
if _, ok := mounts["/mnt/mqm-data"]; ok {
|
||||
args = append(args, "-md", "/mnt/mqm-data/qmgrs")
|
||||
}
|
||||
if os.Getenv("MQ_QMGR_LOG_FILE_PAGES") != "" {
|
||||
_, err = strconv.Atoi(os.Getenv("MQ_QMGR_LOG_FILE_PAGES"))
|
||||
if err != nil {
|
||||
log.Printf("Error processing MQ_QMGR_LOG_FILE_PAGES, the default value for LogFilePages will be used. Err: %v", err)
|
||||
} else {
|
||||
args = append(args, "-lf", os.Getenv("MQ_QMGR_LOG_FILE_PAGES"))
|
||||
}
|
||||
}
|
||||
args = append(args, name)
|
||||
return args
|
||||
}
|
||||
|
||||
func getCreateStandbyQueueManagerArgs(name string) []string {
|
||||
args := []string{"-s", "QueueManager"}
|
||||
args = append(args, "-v", fmt.Sprintf("Name=%v", name))
|
||||
args = append(args, "-v", fmt.Sprintf("Directory=%v", name))
|
||||
args = append(args, "-v", "Prefix=/var/mqm")
|
||||
args = append(args, "-v", fmt.Sprintf("DataPath=/mnt/mqm-data/qmgrs/%v", name))
|
||||
return args
|
||||
}
|
||||
|
||||
// updateQMini removes the original ServicecCmponent stanza so we can add a new one
|
||||
func updateQMini(qmname string) error {
|
||||
|
||||
val, set := os.LookupEnv("MQ_CONNAUTH_USE_HTP")
|
||||
if !set {
|
||||
//simpleauth mode not enabled.
|
||||
return nil
|
||||
}
|
||||
bval, err := strconv.ParseBool(strings.ToLower(val))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bval == false {
|
||||
//simpleauth mode not enabled.
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Removing existing ServiceComponent configuration")
|
||||
|
||||
mounts, err := containerruntime.GetMounts()
|
||||
if err != nil {
|
||||
log.Printf("Error getting mounts for queue manager")
|
||||
return err
|
||||
}
|
||||
dataDir := getQueueManagerDataDir(mounts, replaceCharsInQMName(qmname))
|
||||
qmgrDir := pathutils.CleanPath(dataDir, "qm.ini")
|
||||
//read the initial version.
|
||||
// #nosec G304 - qmgrDir filepath is derived from dspmqinf
|
||||
iniFileBytes, err := os.ReadFile(qmgrDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qminiConfigStr := string(iniFileBytes)
|
||||
if strings.Contains(qminiConfigStr, "ServiceComponent:") {
|
||||
var re = regexp.MustCompile(`(?m)^.*ServiceComponent.*$\s^.*Service.*$\s^.*Name.*$\s^.*Module.*$\s^.*ComponentDataSize.*$`)
|
||||
curFile := re.ReplaceAllString(qminiConfigStr, "")
|
||||
// #nosec G304 G306 - qmgrDir filepath is derived from dspmqinf and
|
||||
// its a read by owner/s group, and pose no harm.
|
||||
err := os.WriteFile(qmgrDir, []byte(curFile), 0660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If queue manager name contains a '.', then the '.' will be replaced with '!'
|
||||
// in the name of the data directory created by the queue manager. Similarly
|
||||
// '/' will be replaced with '&'.
|
||||
func replaceCharsInQMName(qmname string) string {
|
||||
replacedName := qmname
|
||||
if strings.Contains(replacedName, ".") {
|
||||
replacedName = strings.ReplaceAll(replacedName, ".", "!")
|
||||
}
|
||||
if strings.Contains(replacedName, "/") {
|
||||
replacedName = strings.ReplaceAll(replacedName, "/", "&")
|
||||
}
|
||||
return replacedName
|
||||
}
|
||||
122
cmd/runmqserver/qmgr_test.go
Normal file
122
cmd/runmqserver/qmgr_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2023
|
||||
|
||||
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 (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_validateLogFilePageSetting(t *testing.T) {
|
||||
type args struct {
|
||||
iniFilePath string
|
||||
isValid bool
|
||||
logFilePagesValue string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "TestLogFilePages1",
|
||||
args: args{
|
||||
iniFilePath: "./test-files/testvalidateLogFilePages_1.ini",
|
||||
isValid: true,
|
||||
logFilePagesValue: "1235",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestLogFilePages2",
|
||||
args: args{
|
||||
iniFilePath: "./test-files/testvalidateLogFilePages_2.ini",
|
||||
isValid: true,
|
||||
logFilePagesValue: "2224",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestLogFilePages3",
|
||||
args: args{
|
||||
iniFilePath: "./test-files/testvalidateLogFilePages_3.ini",
|
||||
isValid: false,
|
||||
logFilePagesValue: "1235",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestLogFilePages4",
|
||||
args: args{
|
||||
iniFilePath: "./test-files/testvalidateLogFilePages_4.ini",
|
||||
isValid: false,
|
||||
logFilePagesValue: "1235",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestLogFilePages5",
|
||||
args: args{
|
||||
iniFilePath: "./test-files/testvalidateLogFilePages_5.ini",
|
||||
isValid: false,
|
||||
logFilePagesValue: "1235",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
iniFileBytes, err := os.ReadFile(tt.args.iniFilePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validate := validateLogFilePageSetting(iniFileBytes, tt.args.logFilePagesValue)
|
||||
if validate != tt.args.isValid {
|
||||
t.Fatalf("Expected ini file validation output to be %v got %v", tt.args.isValid, validate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test for special character in queue manager names
|
||||
func Test_SpecialCharInQMNameReplacements(t *testing.T) {
|
||||
type qmNames struct {
|
||||
qmName string
|
||||
replacedQMName string
|
||||
}
|
||||
|
||||
tests := []qmNames{
|
||||
{
|
||||
qmName: "QM.",
|
||||
replacedQMName: "QM!",
|
||||
}, {
|
||||
qmName: "QM/",
|
||||
replacedQMName: "QM&",
|
||||
},
|
||||
{
|
||||
qmName: "QM.GR.NAME",
|
||||
replacedQMName: "QM!GR!NAME",
|
||||
}, {
|
||||
qmName: "QM/GR/NAME",
|
||||
replacedQMName: "QM&GR&NAME",
|
||||
}, {
|
||||
qmName: "QMGRNAME",
|
||||
replacedQMName: "QMGRNAME",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
replacedQMName := replaceCharsInQMName(test.qmName)
|
||||
if !strings.EqualFold(replacedQMName, test.replacedQMName) {
|
||||
t.Fatalf("QMName replacement failed. Expected %s but got %s\n", test.replacedQMName, replacedQMName)
|
||||
}
|
||||
}
|
||||
}
|
||||
124
cmd/runmqserver/signals.go
Normal file
124
cmd/runmqserver/signals.go
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/metrics"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
startReaping = iota
|
||||
reapNow = iota
|
||||
)
|
||||
|
||||
func signalHandler(qmgr string, startupCtx context.Context) chan int {
|
||||
control := make(chan int)
|
||||
// Use separate channels for the signals, to avoid SIGCHLD signals swamping
|
||||
// the buffer, and preventing other signals.
|
||||
stopSignals := make(chan os.Signal, 1)
|
||||
reapSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
// Pulling out as function as reused for shutdown and standard control flow
|
||||
processControlSignal := func(job int) {
|
||||
switch {
|
||||
case job == startReaping:
|
||||
// Add SIGCHLD to the list of signals we're listening to
|
||||
log.Debug("Listening for SIGCHLD signals")
|
||||
signal.Notify(reapSignals, syscall.SIGCHLD)
|
||||
case job == reapNow:
|
||||
reapZombies()
|
||||
}
|
||||
}
|
||||
|
||||
// Start handling signals
|
||||
go func() {
|
||||
shutdownCtx, shutdownComplete := context.WithCancel(context.Background())
|
||||
defer func() {
|
||||
shutdownComplete()
|
||||
}()
|
||||
stopTriggered := false
|
||||
for {
|
||||
select {
|
||||
case sig := <-stopSignals:
|
||||
if stopTriggered {
|
||||
continue
|
||||
}
|
||||
log.Printf("Signal received: %v", sig)
|
||||
signal.Stop(stopSignals)
|
||||
stopTriggered = true
|
||||
|
||||
// If a stop signal is received during the startup process continue processing control signals until the main thread marks startup as complete
|
||||
// Don't close the control channel until the main thread has been allowed to finish spawning processes and marks startup as complete
|
||||
// Continue to process job control signals to avoid a deadlock
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
// When the main thread has cancelled the startup context due to completion or an error stop processing control signals
|
||||
case <-startupCtx.Done():
|
||||
done = true
|
||||
// Keep processing control signals until the main thread has finished its startup
|
||||
case job := <-control:
|
||||
processControlSignal(job)
|
||||
}
|
||||
}
|
||||
metrics.StopMetricsGathering(log)
|
||||
|
||||
// Shutdown queue manager in separate goroutine to allow reaping to continue in parallel
|
||||
go func() {
|
||||
_ = stopQueueManager(qmgr)
|
||||
shutdownComplete()
|
||||
}()
|
||||
case <-shutdownCtx.Done():
|
||||
signal.Stop(reapSignals)
|
||||
|
||||
// One final reap
|
||||
// This occurs after all startup processes have been spawned
|
||||
reapZombies()
|
||||
|
||||
close(control)
|
||||
// End the goroutine
|
||||
return
|
||||
case <-reapSignals:
|
||||
log.Debug("Received SIGCHLD signal")
|
||||
reapZombies()
|
||||
case job := <-control:
|
||||
processControlSignal(job)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return control
|
||||
}
|
||||
|
||||
// reapZombies reaps any zombie (terminated) processes now.
|
||||
// This function should be called before exiting.
|
||||
func reapZombies() {
|
||||
for {
|
||||
var ws unix.WaitStatus
|
||||
pid, err := unix.Wait4(-1, &ws, unix.WNOHANG, nil)
|
||||
// If err or pid indicate "no child processes"
|
||||
if pid == 0 || err == unix.ECHILD {
|
||||
return
|
||||
}
|
||||
log.Debugf("Reaped PID %v", pid)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
ExitPath:
|
||||
ExitsDefaultPath=/mnt/mqm/data/exits
|
||||
ExitsDefaultPath64=/mnt/mqm/data/exits64
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogFilePages=1235
|
||||
LogBufferPages=0
|
||||
LogWriteIntegrity=TripleWrite
|
||||
@@ -0,0 +1,9 @@
|
||||
ExitPath:
|
||||
ExitsDefaultPath=/mnt/mqm/data/exits
|
||||
ExitsDefaultPath64=/mnt/mqm/data/exits64
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogFilePages=2224
|
||||
LogBufferPages=0
|
||||
LogWriteIntegrity=TripleWrite
|
||||
@@ -0,0 +1,9 @@
|
||||
ExitPath:
|
||||
ExitsDefaultPath=/mnt/mqm/data/exits
|
||||
ExitsDefaultPath64=/mnt/mqm/data/exits64
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogFilePages=6002
|
||||
LogBufferPages=0
|
||||
LogWriteIntegrity=TripleWrite
|
||||
@@ -0,0 +1,8 @@
|
||||
ExitPath:
|
||||
ExitsDefaultPath=/mnt/mqm/data/exits
|
||||
ExitsDefaultPath64=/mnt/mqm/data/exits64
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogBufferPages=0
|
||||
LogWriteIntegrity=TripleWrite
|
||||
@@ -0,0 +1,8 @@
|
||||
ExitPath:
|
||||
ExitsDefaultPath=/mnt/mqm/data/exits
|
||||
ExitsDefaultPath64=/mnt/mqm/data/exits64
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogBufferPages=1235
|
||||
LogWriteIntegrity=TripleWrite
|
||||
79
cmd/runmqserver/version.go
Normal file
79
cmd/runmqserver/version.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
|
||||
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 (
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqversion"
|
||||
)
|
||||
|
||||
var (
|
||||
// ImageCreated is the date the image was built
|
||||
ImageCreated = "Not specified"
|
||||
// ImageRevision is the source control revision identifier
|
||||
ImageRevision = "Not specified"
|
||||
// ImageSource is the URL to get source code for building the image
|
||||
ImageSource = "Not specified"
|
||||
// ImageTag is the tag of the image
|
||||
ImageTag = "Not specified"
|
||||
)
|
||||
|
||||
func logDateStamp() {
|
||||
log.Printf("Image created: %v", ImageCreated)
|
||||
}
|
||||
|
||||
func logGitRepo() {
|
||||
// log.Printf("Image revision: %v", ImageRevision)
|
||||
}
|
||||
|
||||
func logGitCommit() {
|
||||
// log.Printf("Image source: %v", ImageSource)
|
||||
}
|
||||
|
||||
func logImageTag() {
|
||||
log.Printf("Image tag: %v", ImageTag)
|
||||
}
|
||||
|
||||
func logMQVersion() {
|
||||
mqVersion, err := mqversion.Get()
|
||||
if err != nil {
|
||||
log.Printf("Error Getting MQ version: %v", strings.TrimSuffix(string(mqVersion), "\n"))
|
||||
}
|
||||
|
||||
mqBuild, _, err := command.Run("dspmqver", "-b", "-f", "4")
|
||||
if err != nil {
|
||||
log.Printf("Error Getting MQ build: %v", strings.TrimSuffix(string(mqBuild), "\n"))
|
||||
}
|
||||
mqLicense, _, err := command.Run("dspmqver", "-b", "-f", "8192")
|
||||
if err != nil {
|
||||
log.Printf("Error Getting MQ license: %v", strings.TrimSuffix(string(mqLicense), "\n"))
|
||||
}
|
||||
|
||||
log.Printf("MQ version: %v", strings.TrimSuffix(mqVersion, "\n"))
|
||||
log.Printf("MQ level: %v", strings.TrimSuffix(mqBuild, "\n"))
|
||||
log.Printf("MQ license: %v", strings.TrimSuffix(mqLicense, "\n"))
|
||||
}
|
||||
|
||||
func logVersionInfo() {
|
||||
logDateStamp()
|
||||
logGitRepo()
|
||||
logGitCommit()
|
||||
logImageTag()
|
||||
logMQVersion()
|
||||
}
|
||||
172
cmd/runmqserver/webserver.go
Normal file
172
cmd/runmqserver/webserver.go
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2024
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/copy"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
)
|
||||
|
||||
func startWebServer(webKeystore, webkeystorePW, webTruststoreRef string) error {
|
||||
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
log.Debug("Skipping web server, because it's not installed")
|
||||
return nil
|
||||
}
|
||||
log.Println("Starting web server")
|
||||
// #nosec G204 - command is fixed, no injection vector
|
||||
cmd := exec.Command("strmqweb")
|
||||
|
||||
// Pass all the environment to MQ Web Server JVM
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
// TLS enabled
|
||||
if webKeystore != "" {
|
||||
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTORE="+webKeystore)
|
||||
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTOREPW="+webkeystorePW)
|
||||
cmd.Env = append(cmd.Env, "AMQ_WEBTRUSTSTOREREF="+webTruststoreRef)
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
rc := cmd.ProcessState.ExitCode()
|
||||
if err != nil {
|
||||
log.Printf("Error %v starting web server: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
log.Println("Started web server")
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureWebServer(keyLabel string, p12Truststore tls.KeyStoreData) (string, error) {
|
||||
|
||||
webKeystore := ""
|
||||
|
||||
// Copy server.xml file to ensure that we have the latest expected contents - this file is only populated on QM creation
|
||||
err := copy.CopyFile("/opt/mqm/samp/web/server.xml", "/var/mqm/web/installations/Installation1/servers/mqweb/server.xml")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Configure TLS for the Web Console
|
||||
err = tls.ConfigureWebTLS(keyLabel, log, p12Truststore.Password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Configure the Web Keystore
|
||||
if keyLabel != "" || os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") != "" {
|
||||
webKeystore, err = tls.ConfigureWebKeystore(p12Truststore, keyLabel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = os.Stat("/opt/mqm/bin/strmqweb")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
const webConfigDir string = "/etc/mqm/web"
|
||||
_, err = os.Stat(webConfigDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
const prefix string = "/etc/mqm/web"
|
||||
err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to := fmt.Sprintf("/var/mqm/web%v", from[len(prefix):])
|
||||
exists := true
|
||||
_, err = os.Stat(to)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
exists = false
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if info.IsDir() {
|
||||
if !exists {
|
||||
// #nosec G301 - write group permissions are required
|
||||
err := os.MkdirAll(to, 0770)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if exists {
|
||||
err := os.Remove(to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Use a symlink for file 'mqwebuser.xml', so that if it contains a secret, it doesn't get persisted to a volume
|
||||
if strings.HasSuffix(from, "/mqwebuser.xml") {
|
||||
err = os.Symlink(from, to)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
err := copy.CopyFile(from, to)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return webKeystore, err
|
||||
}
|
||||
|
||||
// Configure FIPS mode for MQ Web Server
|
||||
func configureFIPSWebServer(p12TrustStore tls.KeyStoreData) error {
|
||||
|
||||
// Need to update jvm.options file of MQ Web Server. We don't update the jvm.options file
|
||||
// in /etc/mqm/web/installations/Installation1/servers/mqweb directory. Instead we update
|
||||
// the one in /etc/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults.
|
||||
// During runtime MQ Web Server merges the data from two files.
|
||||
const jvmOptsLink string = "/run/jvm.options"
|
||||
const jvmOptsTemplate string = "/etc/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults/jvm.options.tpl"
|
||||
|
||||
// Update the jvm.options file using the data from template file. Tell the MQ Web Server
|
||||
// use a FIPS provider by setting "-Dcom.ibm.jsse2.usefipsprovider=true" and then tell it
|
||||
// use a specific FIPS provider by setting "Dcom.ibm.jsse2.usefipsProviderName=IBMJCEPlusFIPS".
|
||||
err := mqtemplate.ProcessTemplateFile(jvmOptsTemplate, jvmOptsLink, map[string]string{
|
||||
"FipsProvider": "true",
|
||||
"FipsProviderName": "IBMJCEPlusFIPS",
|
||||
}, log)
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user