package mml import ( "bytes" "encoding/json" "errors" "fmt" "math" "net/http" "regexp" "strconv" "strings" "ems.agt/lib/dborm" "ems.agt/lib/global" "ems.agt/lib/log" "github.com/go-resty/resty/v2" "golang.org/x/crypto/ssh" ) type Param struct { Name string `json:"name"` Value string `json:"value"` } type MmlCommand struct { Operation string `json:"operation"` Object string `json:"object"` Params []Param `json:"params"` PaList []string `json:"paList"` AaList []string `json:"aaList"` AaMap map[string]interface{} `json:"aaMap"` NaMap map[string]interface{} `json:"naMap"` AaUri []string `json:"aaUri"` AaLoc []string `json:"aaLoc"` // for loc parameter } type MmlVar struct { Version string `json:"version"` Output string `json:"output"` Limit int `json:"limit"` User string `json:"user"` Token string `josn:"token"` } var OmcMmlVar *MmlVar func init() { OmcMmlVar = &MmlVar{ Version: "16.1.1", Output: DefaultFormatType, Limit: 50, } } func SetOmcMmlVarOutput(output string) { OmcMmlVar.Output = output } func SetOmcMmlVarLimit(limit int) { OmcMmlVar.Limit = limit } func splitByColon(str string) []string { return splitBy(str, ':') } func splitByComma(str string) []string { return splitBy(str, ',') } func splitBy(str string, sep rune) []string { var result []string var stack []string var current []rune var quotes, apoFlag bool = false, false for _, c := range str { if c == '{' || c == '[' || (c == '\'' && apoFlag == false) || (c == '"' && quotes == false) { // "'" apoFlag = true quotes = true stack = append(stack, string(c)) } else if c == '}' || c == ']' || (c == '\'' && apoFlag == true) || (c == '"' && quotes == true) { apoFlag = false quotes = false if len(stack) > 0 { stack = stack[:len(stack)-1] } } if c == sep && len(stack) == 0 { result = append(result, string(current)) current = []rune{} } else { current = append(current, c) } } result = append(result, string(current)) return result } func ParseMMLCommand(mmlStr string, mmlComms *[]MmlCommand) error { log.Info("ParseMMLCommand processing ...") log.Debug("mmlStr: ", mmlStr) mc := new(MmlCommand) reg := regexp.MustCompile(`\s*;\s*`) mmls := reg.Split(mmlStr, -1) for _, mml := range mmls { log.Trace("mml:", mml) if len(mml) == 0 { continue } //reg := regexp.MustCompile(`\s*:\s*`) //ms := reg.Split(mml, -1) ms := splitByColon(mml) if len(ms) < 1 || len(ms) > 2 { err := global.ErrMmlInvalidCommandFormat log.Error(err) return err } if len(ms) == 2 { cmd := strings.Trim(ms[0], " ") reg = regexp.MustCompile(`\s+`) cs := reg.Split(cmd, -1) //cs := strings.Split(cmd, " ") if len(cs) == 2 { mc.Operation = cs[0] mc.Object = cs[1] } else { err := global.ErrMmlInvalidCommandFormat log.Error(err) return err } //reg = regexp.MustCompile(`\s*,\s*`) //reg = regexp.MustCompile(`(?U)(? 0 { extUri := strings.Join(mml.AaUri, "/") requestURI = requestURI + fmt.Sprintf(mmlMap.ExtUri, extUri) } if mmlMap.Params != "" { params := strings.Join(mml.AaLoc, "+and+") params = strings.ReplaceAll(params, " ", "+") // replace " " to "+" log.Trace("params:", params) if mmlMap.ParamTag == "SQL" && strings.TrimSpace(params) != "" { params = "+where+" + params } requestURI = fmt.Sprintf("%s%s%s", requestURI, mmlMap.Params, params) } return requestURI } func TransMml2HttpReq(sshConn *ssh.ServerConn, httpUri, uerAgent string, mml *MmlCommand) (*[]byte, error) { log.Info("TransMml2HttpReq processing ...") log.Debug("mml: ", mml) where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object) mmlMap, err := dborm.XormGetMmlHttpMap("mml_http_map", where) if err != nil { log.Error("Failed to XormGetMmlHttpMap: ", err) return ParseErrorOutput(err), err } if mmlMap == nil { err := errors.New("Not found mml map") log.Error(err) return ParseErrorOutput(err), err } log.Trace("mmlMap: ", mmlMap) if mmlMap.Output == "" { mmlMap.Output = "{}" } outputJson := new(dborm.MmlOutput) err = json.Unmarshal([]byte(mmlMap.Output), outputJson) if err != nil { log.Error("Failed to Unmarshal:", err) return ParseErrorOutput(err), err } log.Trace("outputJson: ", outputJson) inputJson := new(dborm.MmlInput) log.Trace("mmlMap.Input: ", mmlMap.Input) if mmlMap.Input == "" { mmlMap.Input = "{}" } err = json.Unmarshal([]byte(mmlMap.Input), inputJson) if err != nil { log.Error("Failed to Unmarshal:", err) return ParseErrorOutput(err), err } log.Trace("inputJson: ", inputJson) var requestURI string var output *[]byte client := resty.New() switch strings.ToLower(mmlMap.Method) { case "get": requestURI = parseRequestUri(httpUri, mmlMap, mml) log.Debugf("method: Get requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}). SetHeaders(map[string]string{"User-Agent": uerAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). Get(requestURI) if err != nil { log.Error("Failed to Get:", err) output = ParseErrorOutput(err) } else { output = ParseOutputResponse(outputJson, response) } case "post": requestURI = parseRequestUri(httpUri, mmlMap, mml) body := ParseInputBody(inputJson, mml) log.Debugf("method: Post requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}). SetHeaders(map[string]string{"User-Agent": uerAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). SetBody(*body). Post(requestURI) if err != nil { log.Error("Failed to Post:", err) output = ParseErrorOutput(err) } else { output = ParseOutputResponse(outputJson, response) } case "put": requestURI = parseRequestUri(httpUri, mmlMap, mml) body := ParseInputBody(inputJson, mml) log.Debugf("method: Put requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}). SetHeaders(map[string]string{"User-Agent": uerAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). SetBody(*body). Put(requestURI) if err != nil { log.Error("Failed to Put:", err) output = ParseErrorOutput(err) } else { output = ParseOutputResponse(outputJson, response) } case "delete": requestURI = parseRequestUri(httpUri, mmlMap, mml) log.Debugf("method: Delete requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}). SetHeaders(map[string]string{"User-Agent": uerAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). Delete(requestURI) if err != nil { log.Error("Failed to Delete:", err) output = ParseErrorOutput(err) } else { output = ParseOutputResponse(outputJson, response) } case "patch": requestURI = parseRequestUri(httpUri, mmlMap, mml) log.Debugf("method: patch requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}). SetHeaders(map[string]string{"User-Agent": uerAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). Patch(requestURI) if err != nil { log.Error("Failed to Patch:", err) output = ParseErrorOutput(err) } else { output = ParseOutputResponse(outputJson, response) } default: err := errors.New("not found mml command") log.Error(err) output = ParseErrorOutput(err) } return output, nil } const ( MaxMmlOutputBufferSize = 1000000 FormatTypeJson = "json" FormatTypeTable = "table" DefaultFormatType = FormatTypeTable ) const ( RetCodeSucceeded = 0 RetCodeFailed = 0 ) func ParseInputBody(inputJson *dborm.MmlInput, mml *MmlCommand) *[]byte { inputBody := make(map[string]interface{}) log.Trace("mml.NaMap:", mml.NaMap) log.Trace("mml.AaMap:", mml.AaMap) if strings.ToLower(inputJson.BodyFmt) == "putdb" { for _, icol := range inputJson.Cols { log.Trace("icol:", icol) mml.NaMap[icol.Name] = icol.Value } inputBody[inputJson.BodyKey] = mml.NaMap } else { inputParams := make([]map[string]interface{}, 0) inputParams = append(inputParams, mml.NaMap) inputBody[inputJson.BodyKey] = inputParams } body, err := json.Marshal(inputBody) if err != nil { log.Error("Failed to marshal:", err) } log.Trace("inputBody:", inputBody) log.Trace("body:", string(body)) return &body } func ParseOutputResponse(outputJson *dborm.MmlOutput, response *resty.Response) *[]byte { var output []byte var str bytes.Buffer switch response.StatusCode() { case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted: if OmcMmlVar.Output == FormatTypeJson { code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status()) title := formatTitle(outputJson.Title) json.Indent(&str, response.Body(), "", " ") log.Trace(str.String()) output = global.BytesCombine1([]byte(code), []byte(title), str.Bytes(), []byte("\n")) } else { log.Trace("Body:", string(response.Body())) mapDatas := make(map[string]interface{}, 0) err := json.Unmarshal(response.Body(), &mapDatas) if err != nil { log.Error("Failed to json.Unmarshal:", err) //output = *ParseErrorOutput(err) output = *ParseErrorOutput(string(response.Body())) return &output } log.Trace("mapDatas:", mapDatas) switch strings.ToLower(outputJson.RetFmt) { case "getdb": if len(mapDatas) > 0 { var data interface{} for _, data = range mapDatas { log.Trace("data:", data) break } if len(data.([]interface{})) > 0 { table := (data.([]interface{}))[0] log.Trace("table:", table) code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) title := formatTitle(outputJson.Title) fmtResults := ParseTableOutput(outputJson, table) output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults)) } } case "deletedb": var data interface{} for _, data = range mapDatas { log.Trace("data:", data) break } if len(data.(map[string]interface{})) > 0 { table := data.(map[string]interface{}) code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) fmtResults := ParseDBOperOutput(outputJson, table) output = global.BytesCombine1([]byte(code), []byte(fmtResults)) } case "postdb": var data interface{} for _, data = range mapDatas { log.Trace("data:", data) break } if len(data.(map[string]interface{})) > 0 { table := data.(map[string]interface{}) code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) fmtResults := ParseDBOperOutput(outputJson, table) output = global.BytesCombine1([]byte(code), []byte(fmtResults)) } case "putdb": var data interface{} for _, data = range mapDatas { log.Trace("data:", data) break } if len(data.(map[string]interface{})) > 0 { table := data.(map[string]interface{}) code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) fmtResults := ParseDBOperOutput(outputJson, table) output = global.BytesCombine1([]byte(code), []byte(fmtResults)) } case "getnf": if len(mapDatas) > 0 { var data interface{} for _, data = range mapDatas { log.Trace("data:", data) break } if len(data.([]interface{})) > 0 { //table := (data.([]interface{}))[0] //log.Trace("table:", table) code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) title := formatTitle(outputJson.Title) fmtResults := ParseNFTableOutput(outputJson, data) output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults)) } } default: code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded) output = global.BytesCombine1([]byte(code)) } } default: if OmcMmlVar.Output == FormatTypeJson { code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status()) //title := formatTitle("Network Element Information") json.Indent(&str, response.Body(), "", " ") log.Trace(str.String()) output = global.BytesCombine1([]byte(code), str.Bytes(), []byte("\n")) } else { log.Trace("Body:", string(response.Body())) mapResults := make(map[string]interface{}, 0) err := json.Unmarshal(response.Body(), &mapResults) if err != nil { log.Error("Failed to json.Unmarshal:", err) output = *ParseErrorOutput(string(response.Body())) } else { log.Trace("mapResults:", mapResults) errResult := mapResults["error"] log.Trace("errResult:", errResult) if len(errResult.(map[string]interface{})) > 0 { errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"])) errorInfo := errResult.(map[string]interface{})["errorInfo"] output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) } } } } return &output } func ParseDBOperOutput(outputJson *dborm.MmlOutput, cols any) string { var colOutput []dborm.ColOutput = outputJson.Cols var value, retFmtCols string if len(cols.(map[string]interface{})) > 0 { if len(colOutput) > 0 { coln := colOutput[0].Name value = fmt.Sprintf("%v", cols.(map[string]interface{})[coln]) log.Tracef("coln:%s value:%s", coln, value) retFmtCols = colOutput[0].Display + " = " + value + "\n\n" } } return retFmtCols } func ParseNFTableOutput(outputJson *dborm.MmlOutput, cols any) string { var colOutput []dborm.ColOutput var fmtColName string var colName []string var spaceNum int = 1 var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left" if outputJson.SepSpaceNum != 0 { spaceNum = outputJson.SepSpaceNum } if outputJson.AlignmentM != "" { alignmentM = outputJson.AlignmentM } if outputJson.AlignmentSN != "" { alignmentSN = outputJson.AlignmentSN } if outputJson.AlignmentSV != "" { alignmentSV = outputJson.AlignmentSV } maxLength := math.MinInt64 for _, coln := range outputJson.Cols { log.Trace("coln:", coln) if len(coln.Display) > maxLength { maxLength = len(coln.Display) } if coln.Length < len(coln.Display) { coln.Length = len(coln.Display) } colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display)) colOutput = append(colOutput, coln) } fmtColName = formatLineBySpace(&colName, spaceNum) log.Trace("fmtColName:", fmtColName) var retFmtCols string var fmtColValues []string var numberResult int // for _, colnvs := range cols.([]interface{}) { // log.Trace("colnvs:", colnvs) // if colnvs == nil { // break // } numberResult = len(cols.([]interface{})) if numberResult == 1 && outputJson.SingleList == true { colnv := cols.([]interface{})[0] log.Trace("colnv:", colnv) var fmtNV []string for _, coln := range colOutput { fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display) log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName) value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name]) fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value) fmtNV = append(fmtNV, fmtName+": "+fmtValue) } fmtResults := strings.Join(fmtNV, "\n") log.Tracef("fmtResults:\n%s", fmtResults) fmtEnd := fmt.Sprintf(outputJson.End, numberResult) retFmtCols = fmtResults + "\n\n" + fmtEnd log.Tracef("retFmtCols:\n%s", retFmtCols) return retFmtCols } else { for i := 0; i < numberResult; i++ { colnv := cols.([]interface{})[i] log.Trace("colnv:", colnv) var colValues []string var newVal []string for _, coln := range colOutput { value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name]) if len(coln.Alias) != 0 { enumVal, _ := strconv.Atoi(value) value = parseEnumAlias(&(coln.Alias), enumVal) } newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value)) } colValues = append(colValues, formatLineBySpace(&newVal, spaceNum)) log.Trace("colValues:", colValues) fmtColValues = append(fmtColValues, strings.Join(colValues, "\n")) log.Trace("fmtColValues:", fmtColValues) } fmtEnd := fmt.Sprintf(outputJson.End, numberResult) retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd log.Tracef("retFmtCols:\n%s", retFmtCols) return retFmtCols } } func ParseTableOutput(outputJson *dborm.MmlOutput, cols any) string { var colOutput []dborm.ColOutput var fmtColName string var colName []string var spaceNum int = 1 var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left" if outputJson.SepSpaceNum != 0 { spaceNum = outputJson.SepSpaceNum } if outputJson.AlignmentM != "" { alignmentM = outputJson.AlignmentM } if outputJson.AlignmentSN != "" { alignmentSN = outputJson.AlignmentSN } if outputJson.AlignmentSV != "" { alignmentSV = outputJson.AlignmentSV } maxLength := math.MinInt64 for _, coln := range outputJson.Cols { log.Trace("coln:", coln) if len(coln.Display) > maxLength { maxLength = len(coln.Display) } if coln.Length < len(coln.Display) { coln.Length = len(coln.Display) } colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display)) colOutput = append(colOutput, coln) } fmtColName = formatLineBySpace(&colName, spaceNum) log.Trace("fmtColName:", fmtColName) var retFmtCols string var fmtColValues []string var numberResult int for _, colnvs := range cols.(map[string]interface{}) { log.Trace("colnvs:", colnvs) if colnvs == nil { break } numberResult = len(colnvs.([]interface{})) if numberResult == 1 && outputJson.SingleList == true { colnv := colnvs.([]interface{})[0] log.Trace("colnv:", colnv) var fmtNV []string for _, coln := range colOutput { fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display) log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName) value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name]) fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value) fmtNV = append(fmtNV, fmtName+": "+fmtValue) } fmtResults := strings.Join(fmtNV, "\n") log.Tracef("fmtResults:\n%s", fmtResults) fmtEnd := fmt.Sprintf(outputJson.End, numberResult) retFmtCols = fmtResults + "\n\n" + fmtEnd log.Tracef("retFmtCols:\n%s", retFmtCols) return retFmtCols } else { for i := 0; i < numberResult; i++ { colnv := colnvs.([]interface{})[i] log.Trace("colnv:", colnv) var colValues []string var newVal []string for _, coln := range colOutput { value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name]) if len(coln.Alias) != 0 { enumVal, _ := strconv.Atoi(value) value = parseEnumAlias(&(coln.Alias), enumVal) } newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value)) } colValues = append(colValues, formatLineBySpace(&newVal, spaceNum)) log.Trace("colValues:", colValues) fmtColValues = append(fmtColValues, strings.Join(colValues, "\n")) log.Trace("fmtColValues:", fmtColValues) } fmtEnd := fmt.Sprintf(outputJson.End, numberResult) retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd log.Tracef("retFmtCols:\n%s", retFmtCols) return retFmtCols } } fmtEnd := fmt.Sprintf(outputJson.End, numberResult) retFmtCols = fmtColName + "\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd log.Tracef("retFmtCols:\n%s", retFmtCols) return retFmtCols } func parseEnumAlias(alias *[]string, enumVal int) string { return (*alias)[enumVal] } func formatLineBySpace(strArray *[]string, spaceNum int) string { space := strings.Repeat(" ", spaceNum) return strings.Join(*strArray, space) } func ParseAlignmentOutput(length int, alignment string, str string) string { spaceLen := length - len(str) if spaceLen < 0 { log.Warnf("len(str=%s)=%d more length=%d", str, len(str), length) spaceLen = 0 } var retStr string switch alignment { case "Left": suffix := strings.Repeat(" ", spaceLen) retStr = str + suffix case "Right": prefix := strings.Repeat(" ", spaceLen) retStr = prefix + str log.Tracef("retStr:%s", retStr) case "Middle": prefix := strings.Repeat(" ", int(math.Ceil(float64(spaceLen)/2))) suffix := strings.Repeat(" ", int(math.Floor(float64(spaceLen)/2))) retStr = prefix + str + suffix } log.Tracef("length=%d, spaceLne=%d, alignment=%s, str=%s, retStr=%s", length, spaceLen, alignment, str, retStr) return retStr } func ParseErrorOutput(err any) *[]byte { var output []byte var formatType string = DefaultFormatType if formatType == FormatTypeJson { output = []byte(fmt.Sprintf("ErrorCode = 1 Error message: %v\n\n", err)) } else { output = []byte(fmt.Sprintf("RetCode = -1 operation failed: %v\n\n", err)) } return &output } func formatTitle(title string) string { var builder strings.Builder builder.WriteString(title) builder.WriteString("\n") for i := 0; i < len(title); i++ { builder.WriteString("-") } builder.WriteString("\n") return builder.String() } func formatTableOutput() { }