From 97941bc87c945f2c34c4402ed024065c645466ff Mon Sep 17 00:00:00 2001 From: KiranDarbha <51764601+KiranDarbha@users.noreply.github.com> Date: Tue, 6 Aug 2019 13:47:28 +0530 Subject: [PATCH] Add initial support for INI file merging (#349) --- cmd/runmqserver/main.go | 8 ++ internal/mqini/mqat.ini | 20 +++ internal/mqini/mqini.go | 250 +++++++++++++++++++++++++++++++++++ internal/mqini/mqini_test.go | 140 +++++++++++++++++++- internal/mqini/qm.ini | 45 +++++++ internal/mqini/test1qm.ini | 5 + internal/mqini/test2qm.ini | 7 + internal/mqini/test3qm.ini | 23 ++++ 8 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 internal/mqini/mqat.ini create mode 100644 internal/mqini/qm.ini create mode 100644 internal/mqini/test1qm.ini create mode 100644 internal/mqini/test2qm.ini create mode 100644 internal/mqini/test3qm.ini diff --git a/cmd/runmqserver/main.go b/cmd/runmqserver/main.go index bd79539..1eca829 100644 --- a/cmd/runmqserver/main.go +++ b/cmd/runmqserver/main.go @@ -29,6 +29,7 @@ import ( "github.com/ibm-messaging/mq-container/internal/name" "github.com/ibm-messaging/mq-container/internal/ready" "github.com/ibm-messaging/mq-container/internal/tls" + "github.com/ibm-messaging/mq-container/internal/mqini" ) func doMain() error { @@ -174,6 +175,13 @@ func doMain() error { logTermination(err) return err } + + err = mqini.AddStanzas(name) + if err != nil { + logTermination(err) + return err + } + err = startQueueManager(name) if err != nil { logTermination(err) diff --git a/internal/mqini/mqat.ini b/internal/mqini/mqat.ini new file mode 100644 index 0000000..f36ce8a --- /dev/null +++ b/internal/mqini/mqat.ini @@ -0,0 +1,20 @@ +#*******************************************************************# +#* Module Name: mqat.ini *# +#* Type : IBM MQ queue manager configuration file *# +# Function : Define the configuration of application activity *# +#* trace for a single queue manager. *# +#*******************************************************************# + +# Global settings stanza, default values +AllActivityTrace: + ActivityInterval=1 + ActivityCount=100 + TraceLevel=MEDIUM + TraceMessageData=0 + StopOnGetTraceMsg=ON + SubscriptionDelivery=BATCHED + +# Prevent the sample activity trace program from generating data +ApplicationTrace: + ApplName=amqsact* + Trace=OFF \ No newline at end of file diff --git a/internal/mqini/mqini.go b/internal/mqini/mqini.go index 21d4786..2fc91ab 100644 --- a/internal/mqini/mqini.go +++ b/internal/mqini/mqini.go @@ -19,7 +19,12 @@ package mqini import ( "bufio" + "errors" + "fmt" + "io/ioutil" + "os" "path/filepath" + "regexp" "strings" "github.com/ibm-messaging/mq-container/internal/command" @@ -34,6 +39,12 @@ type QueueManager struct { InstallationName string } +var qmgrDir string + +var stanzasQMINI []string + +var stanzasMQATINI []string + // getQueueManagerFromStanza parses a queue manager stanza func getQueueManagerFromStanza(stanza string) (*QueueManager, error) { scanner := bufio.NewScanner(strings.NewReader(stanza)) @@ -76,3 +87,242 @@ func GetErrorLogDirectory(qm *QueueManager) string { } return filepath.Join(qm.Prefix, "qmgrs", qm.Directory, "errors") } + +//AddStanzas Reads supplied mq ini configuration files and updates the stanzas +//into queue manager's ini configuration files. +func AddStanzas(qmname string) error { + + //find the qmgr directory. + qm, err := GetQueueManager(qmname) + if err != nil { + return err + } + qmgrDir = filepath.Join(qm.Prefix, "qmgrs", qm.Directory) + + //Find the users ini configuration file + files := getIniFileList() + if len(files) > 1 { + msg := fmt.Sprintf("[ %v ]", files) + return errors.New("Only a single ini file can be provided. Following ini files are found:" + msg) + } + if len(files) == 0 { + //no ini file update required. + return nil + } + + iniFileBytes, err := ioutil.ReadFile(files[0]) + if err != nil { + return err + } + userconfig := string(iniFileBytes) + if len(userconfig) == 0 { + return nil + } + + //Prepare a list of all supported stanzas + PopulateAllAvailableStanzas() + + //Update the qmgr ini file with user config. + qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig) + if err != nil { + return err + } + err = writeConfigStanzas(qmConfig, atConfig) + if err != nil { + return err + } + + return nil +} + +// PopulateAllAvailableStanzas initializes the ini stanzas prescribed by mq specification. +func PopulateAllAvailableStanzas() { + stanzasQMINI = []string{"ExitPath", + "Log", + "Service", + "ServiceComponent", + "Channels", + "TCP", + "ApiExitLocal", + "AccessMode", + "RestrictedMode", + "XAResourceManager", + "DefaultBindType", + "SSL", + "DiagnosticMessages", + "Filesystem", + "Security", + "TuningParameters", + "ExitPropertiesLocal", + "LU62", + "NETBIOS"} + + stanzasMQATINI = []string{"AllActivityTrace", "ApplicationTrace"} +} + +// getIniFileList Checks for the user supplied ini file in /etc/mqm directory. +func getIniFileList() []string { + + fileList := []string{} + filepath.Walk("/etc/mqm", func(path string, f os.FileInfo, err error) error { + if strings.HasSuffix(path, ".ini") { + fileList = append(fileList, path) + } + return nil + }) + return fileList +} + +//PrepareConfigStanzasToWrite Reads through the user supplied ini config file and prepares list of +//updates to be written into corresponding mq ini files (qm.ini and/or mqat.ini files.) +func PrepareConfigStanzasToWrite(userconfig string) (string, string, error) { + + var qminiConfigStr string + var mqatiniConfigStr string + + //read the initial version. + iniFileBytes, err := ioutil.ReadFile(filepath.Join(qmgrDir, "qm.ini")) + if err != nil { + return "", "", err + } + qminiConfigStr = string(iniFileBytes) + + iniFileBytes, err = ioutil.ReadFile(filepath.Join(qmgrDir, "mqat.ini")) + if err != nil { + return "", "", err + } + mqatiniConfigStr = string(iniFileBytes) + + stanzaListMerge := make(map[string]strings.Builder) + stanzaListAppend := make(map[string]strings.Builder) + var sbAppend strings.Builder + var sbMerger strings.Builder + + scanner := bufio.NewScanner(strings.NewReader(userconfig)) + scanner.Split(bufio.ScanLines) + consumetoAppend := false + consumeToMerge := false + var stanza string + + //read through the user file and prepare what we want. + for scanner.Scan() { + if strings.Contains(scanner.Text(), ":") { + consumetoAppend = false + consumeToMerge = false + stanza = scanner.Text() + //check if this stanza exists in the qm.ini/mqat.ini files + if strings.Contains(qminiConfigStr, stanza) || + (strings.Contains(mqatiniConfigStr, stanza) && !(strings.Contains(stanza, "ApplicationTrace"))) { + consumeToMerge = true + sbMerger = strings.Builder{} + + stanzaListMerge[stanza] = sbMerger + } else { + consumetoAppend = true + sbAppend = strings.Builder{} + stanzaListAppend[stanza] = sbAppend + } + } else { + if consumetoAppend { + sb := stanzaListAppend[stanza] + sb.WriteString(scanner.Text() + "\n") + stanzaListAppend[stanza] = sb + } + if consumeToMerge { + sb := stanzaListMerge[stanza] + sb.WriteString(scanner.Text() + "\n") + stanzaListMerge[stanza] = sb + } + } + } + + //merge if stanza exits. + if len(stanzaListMerge) > 0 { + for key := range stanzaListMerge { + toWrite, filename := ValidateStanzaToWrite(key) + if toWrite { + attrList := stanzaListMerge[key] + switch filename { + case "qm.ini": + qminiConfigStr = prepareStanzasToMerge(key, attrList, qminiConfigStr) + case "mqat.ini": + mqatiniConfigStr = prepareStanzasToMerge(key, attrList, mqatiniConfigStr) + default: + } + } + } + } + + //append new stanzas. + if len(stanzaListAppend) > 0 { + for key := range stanzaListAppend { + attrList := stanzaListAppend[key] + if strings.Contains(strings.Join(stanzasMQATINI, ", "), strings.TrimSuffix(strings.TrimSpace(key), ":")) { + mqatiniConfigStr = prepareStanzasToAppend(key, attrList, mqatiniConfigStr) + } else { + qminiConfigStr = prepareStanzasToAppend(key, attrList, qminiConfigStr) + } + } + } + + return qminiConfigStr, mqatiniConfigStr, nil +} + +//ValidateStanzaToWrite Validates stanza to be written and the file it belongs to. +func ValidateStanzaToWrite(stanza string) (bool, string) { + stanza = strings.TrimSpace(stanza) + if strings.Contains(stanza, ":") { + stanza = stanza[:len(stanza)-1] + } + + if strings.Contains(strings.Join(stanzasQMINI, ", "), stanza) { + return true, "qm.ini" + } else if strings.Contains(strings.Join(stanzasMQATINI, ", "), stanza) { + return true, "mqat.ini" + } else { + return false, "" + } +} + +//prepareStanzasToAppend Prepares list of stanzas that are to be appended into qm ini files(qm.ini/mqat.ini) +func prepareStanzasToAppend(key string, attrList strings.Builder, iniConfig string) string { + newVal := key + "\n" + attrList.String() + iniConfig = iniConfig + newVal + return iniConfig +} + +//prepareStanzasToMerge Prepares list of stanzas that are to be updated into qm ini files(qm.ini/mqat.ini) +//These stanzas are already present in mq ini files and their values have to be updated with user supplied ini. +func prepareStanzasToMerge(key string, attrList strings.Builder, iniConfig string) string { + lineScanner := bufio.NewScanner(strings.NewReader(attrList.String())) + lineScanner.Split(bufio.ScanLines) + for lineScanner.Scan() { + attrLine := lineScanner.Text() + keyvalue := strings.Split(attrLine, "=") + //this line present in qm.ini, update value. + if strings.Contains(iniConfig, keyvalue[0]) { + re := regexp.MustCompile(keyvalue[0] + "=.*") + iniConfig = re.ReplaceAllString(iniConfig, attrLine) + } else { //this line not present in qm.ini file, add it. + re := regexp.MustCompile(key) + newVal := key + "\n" + attrLine + iniConfig = re.ReplaceAllString(iniConfig, newVal) + } + } + return iniConfig +} + +//writeConfigStanzas Writes the ini file updates into corresponding mq ini files. +func writeConfigStanzas(qmConfig string, atConfig string) error { + + err := ioutil.WriteFile(filepath.Join(qmgrDir, "qm.ini"), []byte(qmConfig), 0644) + if err != nil { + return err + } + + err = ioutil.WriteFile(filepath.Join(qmgrDir, "mqat.ini"), []byte(atConfig), 0644) + if err != nil { + return err + } + return nil +} diff --git a/internal/mqini/mqini_test.go b/internal/mqini/mqini_test.go index ee7c99c..95e85d4 100644 --- a/internal/mqini/mqini_test.go +++ b/internal/mqini/mqini_test.go @@ -1,5 +1,5 @@ /* -© Copyright IBM Corporation 2018 +© 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. @@ -16,7 +16,9 @@ limitations under the License. package mqini import ( + "bufio" "io/ioutil" + "strings" "testing" ) @@ -62,3 +64,139 @@ func TestGetQueueManager(t *testing.T) { }) } } + +func TestIniFileStanzas(t *testing.T) { + PopulateAllAvailableStanzas() + + checkReturns("ApiExitLocal", true, true, t) + checkReturns("Channels", true, true, t) + checkReturns("TCP", true, true, t) + checkReturns("ServiceComponent", true, true, t) + checkReturns("Service", true, true, t) + checkReturns("AccessMode", true, true, t) + checkReturns("RestrictedMode", true, true, t) + checkReturns("XAResourceManager", true, true, t) + checkReturns("SSL", true, true, t) + checkReturns("Security", true, true, t) + checkReturns("TuningParameters", true, true, t) + checkReturns("ABC", false, false, t) + checkReturns("#1234ABD", true, false, t) + checkReturns("AllActivityTrace", false, true, t) + checkReturns("ApplicationTrace", false, true, t) + checkReturns("xyz123abvc", false, false, t) +} + +func TestIniFile1Update(t *testing.T) { + + iniFileBytes, err := ioutil.ReadFile("test1qm.ini") + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + userconfig := string(iniFileBytes) + qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig) + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + if len(atConfig) == 0 { + t.Errorf("Unexpected stanza file update: mqat.ini[%s]\n", atConfig) + } + if len(qmConfig) == 0 { + t.Errorf("Expected stanza file not found: qm.ini\n") + } + + scanner := bufio.NewScanner(strings.NewReader(userconfig)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + line := scanner.Text() + if !strings.Contains(qmConfig, line) { + t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig) + break + } + } +} + +func TestIniFile2Update(t *testing.T) { + + iniFileBytes, err := ioutil.ReadFile("test2qm.ini") + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + userconfig := string(iniFileBytes) + qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig) + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + if len(atConfig) == 0 { + t.Errorf("Expected stanza file not found: mqat.ini\n") + } + if len(qmConfig) == 0 { + t.Errorf("Expected stanza file not found: qm.ini\n") + } + + scanner := bufio.NewScanner(strings.NewReader(userconfig)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + line := scanner.Text() + if !strings.Contains(atConfig, line) { + t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig) + break + } + } +} + +func TestIniFile3Update(t *testing.T) { + + i := 0 + iniFileBytes, err := ioutil.ReadFile("test3qm.ini") + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + userconfig := string(iniFileBytes) + qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig) + if err != nil { + t.Errorf("Unexpected error: [%s]\n", err.Error()) + } + if len(qmConfig) == 0 { + t.Errorf("Unexpected stanza file update: qm.ini[%s]\n", atConfig) + } + if len(atConfig) == 0 { + t.Errorf("Expected stanza file not found: mqat.ini\n") + } + + scanner := bufio.NewScanner(strings.NewReader(userconfig)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + line := scanner.Text() + i++ + //first 20 lines of test3qm.ini shall go into qm.ini file and rest into mqat.ini file. + if i < 20 { + if !strings.Contains(qmConfig, line) { + t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig) + } + } else if i > 20 { + if !strings.Contains(atConfig, line) { + t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig) + } + } + } +} + +func checkReturns(stanza string, isqmini bool, shouldexist bool, t *testing.T) { + + exists, filename := ValidateStanzaToWrite(stanza) + if exists != shouldexist { + t.Errorf("Stanza should exist %t but found was %t", shouldexist, exists) + } + + if shouldexist { + if isqmini { + if filename != "qm.ini" { + t.Errorf("Expected filename:qm.ini for stanza:%s. But got %s", stanza, filename) + } + } else { + if filename != "mqat.ini" { + t.Errorf("Expected filename:mqat.ini for stanza:%s. But got %s", stanza, filename) + } + } + } +} diff --git a/internal/mqini/qm.ini b/internal/mqini/qm.ini new file mode 100644 index 0000000..a344afe --- /dev/null +++ b/internal/mqini/qm.ini @@ -0,0 +1,45 @@ +#*******************************************************************# +#* Module Name: qm.ini *# +#* Type : IBM MQ queue manager configuration file *# +# Function : Define the configuration of a single queue manager *# +#* *# +#*******************************************************************# +#* Notes : *# +#* 1) This file defines the configuration of the queue manager *# +#* *# +#*******************************************************************# +ExitPath: + ExitsDefaultPath=C:\ProgramData\IBM\MQ\exits + ExitsDefaultPath64=C:\ProgramData\IBM\MQ\exits64 +InstanceData: + InstanceID=1562831591 + Startup=ServiceManual +#* *# +#* *# +Log: + LogPrimaryFiles=3 + LogSecondaryFiles=2 + LogFilePages=4096 + LogType=CIRCULAR + LogBufferPages=0 + LogPath=C:\ProgramData\IBM\MQ\log\INI1\ + LogWriteIntegrity=TripleWrite +Service: + Name=AuthorizationService + EntryPoints=14 +ServiceComponent: + Service=AuthorizationService + Name=MQSeries.WindowsNT.auth.service + Module=amqzfu.dll + ComponentDataSize=0 +Channels: + ChlauthEarlyAdopt=Y +TCP: + SndBuffSize=0 + RcvBuffSize=0 + RcvSndBuffSize=0 + RcvRcvBuffSize=0 + ClntSndBuffSize=0 + ClntRcvBuffSize=0 + SvrSndBuffSize=0 + SvrRcvBuffSize=0 diff --git a/internal/mqini/test1qm.ini b/internal/mqini/test1qm.ini new file mode 100644 index 0000000..ffeba33 --- /dev/null +++ b/internal/mqini/test1qm.ini @@ -0,0 +1,5 @@ +ApiExitLocal:    + Sequence=1 + Function=EntryPoint + Module=/opt/mylibs/mylib.so + Name=mylib \ No newline at end of file diff --git a/internal/mqini/test2qm.ini b/internal/mqini/test2qm.ini new file mode 100644 index 0000000..734f9d9 --- /dev/null +++ b/internal/mqini/test2qm.ini @@ -0,0 +1,7 @@ +AllActivityTrace: + ActivityInterval=11 + ActivityCount=1 + TraceLevel=INFO +ApplicationTrace: + ApplName=amqsget + Trace=ON \ No newline at end of file diff --git a/internal/mqini/test3qm.ini b/internal/mqini/test3qm.ini new file mode 100644 index 0000000..f745a68 --- /dev/null +++ b/internal/mqini/test3qm.ini @@ -0,0 +1,23 @@ +ApiExitLocal:    + Sequence=1 + Function=EntryPoint + Module=/opt/MQOpenTracing/MQOpenTracingExit.so + Name=MQOpenTracingExit +Channels: + MQIBindType=FASTPATH +Log: + LogPrimaryFiles=30 + LogType=CIRCULAR + LogPath=/ProgramfILES/IBM/MQ/log/INI1/ +TCP: + SndBuffSize=4095 + RcvBuffSize=4095 + RcvSndBuffSize=4095 + RcvRcvBuffSize=4095 + ClntSndBuffSize=2049 + ClntRcvBuffSize=2049 + SvrSndBuffSize=2049 + SvrRcvBuffSize=2049 +ApplicationTrace: + ApplName=amqsput + Trace=ON