pkg/mgmapi/config_reader.go (156 lines of code) (raw):

// Copyright (c) 2021, Oracle and/or its affiliates. // // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ package mgmapi import ( "encoding/base64" "encoding/binary" "github.com/mysql/ndb-operator/config/debug" ) type configValue interface{} // configReader helps in extracting config information from a given binary config data type configReader struct { // raw data and offset data []byte offset uint32 // extracted information totalLengthInWords, version uint32 numOfDefaultSections, numOfNodes, numOfCommSections uint32 value configValue } // getNewConfigReader creates a new configReader // for the given base 64 encoded config data func getNewConfigReader(base64EncodedData string) *configReader { // decode the string decodedData, err := base64.StdEncoding.DecodeString(base64EncodedData) if err != nil { debug.Panic("failed to decode config string : " + err.Error()) return nil } return &configReader{data: decodedData} } // readUint32 reads an uint32 from the data at the current offset func (cr *configReader) readUint32() uint32 { v := binary.BigEndian.Uint32(cr.data[cr.offset : cr.offset+4]) cr.offset += 4 return v } // readString reads a string from the data at the current offset func (cr *configReader) readString() string { // Extract the length of the string strLen := cr.readUint32() // Extract the string. // Null character is present at the end and should not be read. s := string(cr.data[cr.offset : cr.offset+strLen-1]) // update offset strLen = strLen + ((4 - (strLen & 3)) & 3) cr.offset += strLen return s } // constants required for extracting the entry key and values const ( v2TypeShift = 28 v2TypeMask = 15 v2KeyShift = 0 v2KeyMask = 0x0FFFFFFF ) // Config entry value types used by readEntry const ( // CfgEntryTypeInvalid = 0 CfgEntryTypeInt = 1 CfgEntryTypeString = 2 // CfgEntryTypeSection = 3 CfgEntryTypeInt64 = 4 ) // readEntry reads an entry from the data at the current offset func (cr *configReader) readEntry() (uint32, configValue) { // Extract key and key type keyAndType := cr.readUint32() key := (keyAndType >> v2KeyShift) & v2KeyMask keyType := (keyAndType >> v2TypeShift) & v2TypeMask // Read the value based on the key type var value interface{} switch keyType { case CfgEntryTypeInt: { value = cr.readUint32() } case CfgEntryTypeInt64: { high := cr.readUint32() low := cr.readUint32() value = (uint64(high) << 32) + uint64(low) } case CfgEntryTypeString: { value = cr.readString() } default: debug.Panic("unsupported keyType in readEntry()") } return key, value } // cfgSectionType is the section types used by the config section type cfgSectionType int const ( // cfgSectionTypeInvalid cfgSectionType = 0 cfgSectionTypeNDB cfgSectionType = 1 // cfgSectionTypeAPI cfgSectionType = 2 cfgSectionTypeMGM cfgSectionType = 3 // cfgSectionTypeTCP cfgSectionType = 4 // cfgSectionTypeSHM cfgSectionType = 5 cfgSectionTypeSystem cfgSectionType = 6 ) // readEntryFromSection reads a section from the config and extracts // the value of the configKey entry. It allows certain filters like // the sectionTypeFilter, nodeIdFilter, which when passed will have // to match the section being read or else, the section is skipped. func (cr *configReader) readEntryFromSection( isNodesSection bool, sectionTypeFilter cfgSectionType, nodeIdFilter uint32, configKey uint32) (stopReading bool) { // read the header sectionLength := cr.readUint32() numOfEntries := int(cr.readUint32()) sectionType := cfgSectionType(cr.readUint32()) // Calculate the offset required to skip the section. // Increment the current offset by sectionLength in bytes // minus the header size(3 words), as they have been read already. endOfSectionOffset := cr.offset + (sectionLength-3)*4 if sectionType != sectionTypeFilter { // Caller is not interested in this type of section - skip reading configSection. cr.offset = endOfSectionOffset return false } // put the entries that need to be extracted in a map configValues := make(map[uint32]interface{}, 2) configValues[configKey] = nil if isNodesSection { // This is a node section, read the nodeId configValues[nodeCfgNodeId] = nil } // read the required entries and extract the values // stop reading the section as soon as the required values are extracted numOfValuesExtracted := 0 for i := 0; i < numOfEntries && numOfValuesExtracted < len(configValues); i++ { key, value := cr.readEntry() if _, exists := configValues[key]; exists { // this entry needs to be stored configValues[key] = value numOfValuesExtracted++ } } // update offset to mark end of section cr.offset = endOfSectionOffset if !isNodesSection { // A default section is being read // Store the extracted value in configReader and return cr.value = configValues[configKey] // stop reading if this is a system section, if not continue return sectionType == cfgSectionTypeSystem } // Node Section sectionNodeId := configValues[nodeCfgNodeId] if sectionNodeId == nil { debug.Panic("nodeId not found in section") return true } if nodeIdFilter != 0 && nodeIdFilter != sectionNodeId.(uint32) { // Caller not interested in this node return false } // store the extracted value if it is not nil extractedValue := configValues[configKey] if extractedValue != nil { cr.value = extractedValue } // stop reading config as both the default, and the desired node section have been read return true } // readConfig reads the config entry from the binary data and returns configValue func (cr *configReader) readConfig(sectionTypeFilter cfgSectionType, fromNodeId, configKey uint32) configValue { // Start reading the header. // // Magic Word (2 words) - ignored // Header section (7 words) : // 1. Total length in words of configuration binary // 2. Configuration binary version (this is version 2) // 3. Number of default sections in configuration binary // - Data node defaults // - API node defaults // - MGM server node defaults // - TCP communication defaults // - SHM communication defaults // So, the value is always 5 in this version // 4. Number of data nodes // 5. Number of API nodes // 6. Number of MGM server nodes // 7. Number of communication sections // read the header - starts after the magic word cr.offset = 8 defer func() { // reset offset after reading cr.offset = 0 }() cr.totalLengthInWords = cr.readUint32() cr.version = cr.readUint32() // configReader can handle only version 2 if cr.version != 2 { debug.Panic("unexpected version in get config reply") return nil } cr.numOfDefaultSections = cr.readUint32() for i := 0; i < 3; i++ { cr.numOfNodes += cr.readUint32() } cr.numOfCommSections = cr.readUint32() // Start reading the sections // // 1. The default sections // - Data node defaults // - API node defaults // - MGM server node defaults // - TCP communication defaults // - SHM communication defaults // 2. System section // 3. Node sections // (Ordered based on node ids of nodes of all types) // 4. Communication sections - ignored // start reading the sections // default sections for ds := 0; ds < int(cr.numOfDefaultSections); ds++ { // no need to handle the return value as the reading has to continue anyways cr.readEntryFromSection(false, sectionTypeFilter, 0, configKey) } // system section if cr.readEntryFromSection(false, sectionTypeFilter, 0, configKey) { return cr.value } // node sections for n := 0; n < int(cr.numOfNodes); n++ { if cr.readEntryFromSection(true, sectionTypeFilter, fromNodeId, configKey) { return cr.value } } // control should never reach here if the method is used right debug.Panic("failed to find the desired config key") return nil } // readConfigFromBase64EncodedData extracts and returns the config value // of configKey from the given base64 encoded binary config data. func readConfigFromBase64EncodedData( base64EncodedData string, sectionTypeFilter cfgSectionType, fromNodeId, configKey uint32) configValue { // create a config reader and read the data cr := getNewConfigReader(base64EncodedData) if cr == nil { return nil } return cr.readConfig(sectionTypeFilter, fromNodeId, configKey) }