diff --git a/CHANGELOG.md b/CHANGELOG.md index 87d7e2c9..fb1a9637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change log +## XX (2025-XX) +* New environment variables: MQ_QMGR_PRIMARY_LOGFILES, MQ_QMGR_SECONDARY_LOGFILES. These are meant to be set the number of logfiles when the default ones are not enough. + ## 9.4.2.0 (2025-02) * Updated to MQ version 9.4.2.0 diff --git a/README.md b/README.md index 7cd4ce0e..85f66174 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Note that in order to use the image, it is necessary to accept the terms of the - **LANG** - Set this to the language you would like the license to be printed in. - **MQ_QMGR_NAME** - Set this to the name you want your Queue Manager to be created with. - **MQ_QMGR_LOG_FILE_PAGES** - Set this to control the value for LogFilePages passed to the "crtmqm" command. Cannot be changed after queue manager creation. +- **MQ_QMGR_PRIMARY_LOGFILES** - Set this to control the value for LogPrimaryFiles passed to the "crtmqm" command. Cannot be changed after queue manager creation. +- **MQ_QMGR_SECONDARY_LOGFILES** - Set this to control the value for LogSecondaryFiles passed to the "crtmqm" command. Cannot be changed after queue manager creation. - **MQ_LOGGING_CONSOLE_SOURCE** - Specifies a comma-separated list of sources for logs which are mirrored to the container's stdout. The valid values are "qmgr", "web" and "mqsc". Defaults to "qmgr,web". - **MQ_LOGGING_CONSOLE_FORMAT** - Changes the format of the logs which are printed on the container's stdout. Set to "json" to use JSON format (JSON object per line); set to "basic" to use a simple human-readable format. Defaults to "basic". - **MQ_LOGGING_CONSOLE_EXCLUDE_ID** - Excludes log messages with the specified ID. The log messages still appear in the log file on disk, but are excluded from the container's stdout. Defaults to "AMQ5041I,AMQ5052I,AMQ5051I,AMQ5037I,AMQ5975I". diff --git a/cmd/runmqserver/qmgr.go b/cmd/runmqserver/qmgr.go index 6984c9e9..557cc69e 100644 --- a/cmd/runmqserver/qmgr.go +++ b/cmd/runmqserver/qmgr.go @@ -82,6 +82,30 @@ func createQueueManager(name string, devMode bool) (bool, error) { 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.") } } + // Check if MQ_QMGR_PRIMARY_LOGFILES matches the value set in qm.ini + plf := os.Getenv("MQ_QMGR_PRIMARY_LOGFILES") + if plf != "" { + qmIniBytes, err := readQMIni(dataDir) + if err != nil { + log.Printf("Error reading qm.ini : %v", err) + return false, err + } + if !validatePrimaryLogFileSetting(qmIniBytes, plf) { + log.Println("Warning: the value of MQ_QMGR_PRIMARY_LOGFILES does not match the value of 'LogPrimaryFiles' in the qm.ini. This setting cannot be altered after Queue Manager creation.") + } + } + // Check if MQ_QMGR_SECONDARY_LOGFILES matches the value set in qm.ini + slf := os.Getenv("MQ_QMGR_SECONDARY_LOGFILES") + if slf != "" { + qmIniBytes, err := readQMIni(dataDir) + if err != nil { + log.Printf("Error reading qm.ini : %v", err) + return false, err + } + if !validateSecondaryLogFileSetting(qmIniBytes, slf) { + log.Println("Warning: the value of MQ_QMGR_SECONDARY_LOGFILES does not match the value of 'LogSecondaryFiles' in the qm.ini. This setting cannot be altered after Queue Manager creation.") + } + } return false, nil } @@ -131,6 +155,20 @@ func validateLogFilePageSetting(iniFileBytes []byte, logFilePages string) bool { return strings.Contains(qminiConfigStr, lfpString) } +// validatePrimaryLogFileSetting validates if the specified primaryLogFiles number is equal to the existing value in the qm.ini +func validatePrimaryLogFileSetting(iniFileBytes []byte, primaryLogFiles string) bool { + plfString := "LogPrimaryFiles=" + primaryLogFiles + qminiConfigStr := string(iniFileBytes) + return strings.Contains(qminiConfigStr, plfString) +} + +// validateSecondaryLogFileSetting validates if the specified secondaryLogFiles number is equal to the existing value in the qm.ini +func validateSecondaryLogFileSetting(iniFileBytes []byte, secondaryLogFiles string) bool { + slfString := "LogSecondaryFiles=" + secondaryLogFiles + qminiConfigStr := string(iniFileBytes) + return strings.Contains(qminiConfigStr, slfString) +} + func updateCommandLevel() error { level, ok := os.LookupEnv("MQ_CMDLEVEL") if ok && level != "" { @@ -286,6 +324,22 @@ func getCreateQueueManagerArgs(mounts map[string]string, name string, devMode bo args = append(args, "-lf", os.Getenv("MQ_QMGR_LOG_FILE_PAGES")) } } + if os.Getenv("MQ_QMGR_PRIMARY_LOGFILES") != "" { + _, err = strconv.Atoi(os.Getenv("MQ_QMGR_PRIMARY_LOGFILES")) + if err != nil { + log.Printf("Error processing MQ_QMGR_PRIMARY_LOGFILES, the default value for LogPrimaryFiles will be used. Err: %v", err) + } else { + args = append(args, "-lp", os.Getenv("MQ_QMGR_PRIMARY_LOGFILES")) + } + } + if os.Getenv("MQ_QMGR_SECONDARY_LOGFILES") != "" { + _, err = strconv.Atoi(os.Getenv("MQ_QMGR_SECONDARY_LOGFILES")) + if err != nil { + log.Printf("Error processing MQ_QMGR_SECONDARY_LOGFILES, the default value for LogSecondaryFiles will be used. Err: %v", err) + } else { + args = append(args, "-ls", os.Getenv("MQ_QMGR_SECONDARY_LOGFILES")) + } + } args = append(args, name) return args } diff --git a/cmd/runmqserver/qmgr_test.go b/cmd/runmqserver/qmgr_test.go index 81832755..cecbf406 100644 --- a/cmd/runmqserver/qmgr_test.go +++ b/cmd/runmqserver/qmgr_test.go @@ -86,6 +86,136 @@ func Test_validateLogFilePageSetting(t *testing.T) { } } +func Test_validatePrimaryLogFileSetting(t *testing.T) { + type args struct { + iniFilePath string + isValid bool + primaryLogFilesValue string + } + tests := []struct { + name string + args args + }{ + { + name: "TestPrimaryLogFile1", + args: args{ + iniFilePath: "./test-files/testvalidateLogPrimaryFiles_1.ini", + isValid: true, + primaryLogFilesValue: "3", + }, + }, + { + name: "TestPrimaryLogFile2", + args: args{ + iniFilePath: "./test-files/testvalidateLogPrimaryFiles_2.ini", + isValid: true, + primaryLogFilesValue: "3", + }, + }, + { + name: "TestPrimaryLogFile3", + args: args{ + iniFilePath: "./test-files/testvalidateLogPrimaryFiles_3.ini", + isValid: false, + primaryLogFilesValue: "10", + }, + }, + { + name: "TestPrimaryLogFile4", + args: args{ + iniFilePath: "./test-files/testvalidateLogPrimaryFiles_4.ini", + isValid: false, + primaryLogFilesValue: "3", + }, + }, + { + name: "TestPrimaryLogFile5", + args: args{ + iniFilePath: "./test-files/testvalidateLogPrimaryFiles_5.ini", + isValid: false, + primaryLogFilesValue: "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 := validatePrimaryLogFileSetting(iniFileBytes, tt.args.primaryLogFilesValue) + if validate != tt.args.isValid { + t.Fatalf("Expected ini file validation output to be %v got %v", tt.args.isValid, validate) + } + }) + } +} + +func Test_validateSecondaryLogFileSetting(t *testing.T) { + type args struct { + iniFilePath string + isValid bool + secondaryLogFilesValue string + } + tests := []struct { + name string + args args + }{ + { + name: "TestSecondaryLogFile1", + args: args{ + iniFilePath: "./test-files/testvalidateLogSecondaryFiles_1.ini", + isValid: true, + secondaryLogFilesValue: "2", + }, + }, + { + name: "TestSecondaryLogFile2", + args: args{ + iniFilePath: "./test-files/testvalidateLogSecondaryFiles_2.ini", + isValid: true, + secondaryLogFilesValue: "2", + }, + }, + { + name: "TestSecondaryLogFile3", + args: args{ + iniFilePath: "./test-files/testvalidateLogSecondaryFiles_3.ini", + isValid: false, + secondaryLogFilesValue: "10", + }, + }, + { + name: "TestSecondaryLogFile4", + args: args{ + iniFilePath: "./test-files/testvalidateLogSecondaryFiles_4.ini", + isValid: false, + secondaryLogFilesValue: "2", + }, + }, + { + name: "TestSecondaryLogFile5", + args: args{ + iniFilePath: "./test-files/testvalidateLogSecondaryFiles_5.ini", + isValid: false, + secondaryLogFilesValue: "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 := validateSecondaryLogFileSetting(iniFileBytes, tt.args.secondaryLogFilesValue) + 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 { diff --git a/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_1.ini b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_1.ini new file mode 100644 index 00000000..eb9ec817 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_1.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_2.ini b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_2.ini new file mode 100644 index 00000000..1938f306 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_2.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_3.ini b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_3.ini new file mode 100644 index 00000000..b63b31d6 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_3.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_4.ini b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_4.ini new file mode 100644 index 00000000..17daf768 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_4.ini @@ -0,0 +1,7 @@ +ExitPath: + ExitsDefaultPath=/mnt/mqm/data/exits + ExitsDefaultPath64=/mnt/mqm/data/exits64 +Log: + LogSecondaryFiles=2 + LogBufferPages=0 + LogWriteIntegrity=TripleWrite \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_5.ini b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_5.ini new file mode 100644 index 00000000..2b3dc36d --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogPrimaryFiles_5.ini @@ -0,0 +1,7 @@ +ExitPath: + ExitsDefaultPath=/mnt/mqm/data/exits + ExitsDefaultPath64=/mnt/mqm/data/exits64 +Log: + LogSecondaryFiles=2 + LogBufferPages=3 + LogWriteIntegrity=TripleWrite \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_1.ini b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_1.ini new file mode 100644 index 00000000..eb9ec817 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_1.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_2.ini b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_2.ini new file mode 100644 index 00000000..1938f306 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_2.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_3.ini b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_3.ini new file mode 100644 index 00000000..b63b31d6 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_3.ini @@ -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 \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_4.ini b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_4.ini new file mode 100644 index 00000000..b546d1c9 --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_4.ini @@ -0,0 +1,6 @@ +ExitPath: + ExitsDefaultPath=/mnt/mqm/data/exits + ExitsDefaultPath64=/mnt/mqm/data/exits64 +Log: + LogBufferPages=0 + LogWriteIntegrity=TripleWrite \ No newline at end of file diff --git a/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_5.ini b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_5.ini new file mode 100644 index 00000000..e056ae1f --- /dev/null +++ b/cmd/runmqserver/test-files/testvalidateLogSecondaryFiles_5.ini @@ -0,0 +1,6 @@ +ExitPath: + ExitsDefaultPath=/mnt/mqm/data/exits + ExitsDefaultPath64=/mnt/mqm/data/exits64 +Log: + LogBufferPages=12345 + LogWriteIntegrity=TripleWrite \ No newline at end of file diff --git a/test/container/docker_api_test.go b/test/container/docker_api_test.go index 82f68215..7cc84539 100644 --- a/test/container/docker_api_test.go +++ b/test/container/docker_api_test.go @@ -108,6 +108,14 @@ func goldenPath(t *testing.T, metrics bool) { t.Run("Validate Default LogFilePages", func(t *testing.T) { testLogFilePages(t, cli, id, "qm1", "4096") }) + + t.Run("Validate Default LogPrimaryFiles", func(t *testing.T) { + testPrimaryLogFiles(t, cli, id, "qm1", "3") + }) + + t.Run("Validate Default LogSecondaryFiles", func(t *testing.T) { + testSecondaryLogFiles(t, cli, id, "qm1", "2") + }) // Stop the container cleanly stopContainer(t, cli, id) } @@ -1407,6 +1415,38 @@ func TestCustomLogFilePages(t *testing.T) { testLogFilePages(t, cli, id, "qmlfp", "8192") } +// TestCustomPrimaryLogFiles starts a qmgr with a custom number of LogPrimaryFiles set. +// Check that the number of LogPrimaryFiles matches. +func TestCustomPrimaryLogFiles(t *testing.T) { + t.Parallel() + cli := ce.NewContainerClient(ce.WithTestCommandLogger(t)) + containerConfig := ce.ContainerConfig{ + Env: []string{"LICENSE=accept", "MQ_QMGR_PRIMARY_LOGFILES=16", "MQ_QMGR_NAME=qmlfp"}, + } + + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id, false) + waitForReady(t, cli, id) + + testPrimaryLogFiles(t, cli, id, "qmlfp", "16") +} + +// TestCustomSecondaryLogFiles starts a qmgr with a custom number of LogSecondaryFiles set. +// Check that the number of LogSecondaryFiles matches. +func TestCustomSecondaryLogFiles(t *testing.T) { + t.Parallel() + cli := ce.NewContainerClient(ce.WithTestCommandLogger(t)) + containerConfig := ce.ContainerConfig{ + Env: []string{"LICENSE=accept", "MQ_QMGR_SECONDARY_LOGFILES=8", "MQ_QMGR_NAME=qmlfp"}, + } + + id := runContainer(t, cli, &containerConfig) + defer cleanContainer(t, cli, id, false) + waitForReady(t, cli, id) + + testSecondaryLogFiles(t, cli, id, "qmlfp", "8") +} + // TestLoggingConsoleSource tests default behavior which is // MQ_LOGGING_CONSOLE_SOURCE set to qmgr,web func TestLoggingConsoleSource(t *testing.T) { diff --git a/test/container/docker_api_test_util.go b/test/container/docker_api_test_util.go index 833a2b45..c78fcbaf 100644 --- a/test/container/docker_api_test_util.go +++ b/test/container/docker_api_test_util.go @@ -974,6 +974,26 @@ func testLogFilePages(t *testing.T, cli ce.ContainerInterface, id string, qmName } } +// testPrimaryLogFiles validates that the specified number of LogPrimaryFiles is present in the qm.ini file. +func testPrimaryLogFiles(t *testing.T, cli ce.ContainerInterface, id string, qmName string, expectedPrimaryLogFiles string) { + catIniFileCommand := fmt.Sprintf("cat /var/mqm/qmgrs/" + qmName + "/qm.ini") + _, iniContent := execContainer(t, cli, id, "", []string{"bash", "-c", catIniFileCommand}) + + if !strings.Contains(iniContent, "LogPrimaryFiles="+expectedPrimaryLogFiles) { + t.Errorf("Expected qm.ini to contain LogPrimaryFiles="+expectedPrimaryLogFiles+"; got qm.ini \"%v\"", iniContent) + } +} + +// testSecondaryLogFiles validates that the specified number of LogSecondaryFiles is present in the qm.ini file. +func testSecondaryLogFiles(t *testing.T, cli ce.ContainerInterface, id string, qmName string, expectedSecondaryLogFiles string) { + catIniFileCommand := fmt.Sprintf("cat /var/mqm/qmgrs/" + qmName + "/qm.ini") + _, iniContent := execContainer(t, cli, id, "", []string{"bash", "-c", catIniFileCommand}) + + if !strings.Contains(iniContent, "LogSecondaryFiles="+expectedSecondaryLogFiles) { + t.Errorf("Expected qm.ini to contain LogSecondaryFiles="+expectedSecondaryLogFiles+"; got qm.ini \"%v\"", iniContent) + } +} + // waitForMessageInLog will check for a particular message with wait func waitForMessageInLog(t *testing.T, cli ce.ContainerInterface, id string, expectedMessageId string) (string, error) { var jsonLogs string