From 61d193d55083989035fb51534467f5cde3f44bfc Mon Sep 17 00:00:00 2001 From: Seokho Son Date: Thu, 16 May 2024 04:21:23 +0900 Subject: [PATCH] Provisioning MCIS dynamic with hold option --- src/api/rest/docs/docs.go | 16 +++++++++- src/api/rest/docs/swagger.json | 16 +++++++++- src/api/rest/docs/swagger.yaml | 10 +++++++ src/api/rest/server/mcis/control.go | 6 ++-- src/api/rest/server/mcis/provisioning.go | 4 ++- src/core/common/config.go | 5 ++++ src/core/common/utility.go | 7 +++-- src/core/mcir/common.go | 11 ++++--- src/core/mcir/spec.go | 2 +- src/core/mcis/control.go | 14 +++++++++ src/core/mcis/nlb.go | 2 +- src/core/mcis/provisioning.go | 38 ++++++++++++++++++++++-- src/main.go | 2 +- 13 files changed, 112 insertions(+), 21 deletions(-) diff --git a/src/api/rest/docs/docs.go b/src/api/rest/docs/docs.go index c9fc11aa..7bec4a87 100644 --- a/src/api/rest/docs/docs.go +++ b/src/api/rest/docs/docs.go @@ -2021,7 +2021,9 @@ const docTemplate = `{ "resume", "reboot", "terminate", - "refine" + "refine", + "continue", + "withdraw" ], "type": "string", "description": "Action to MCIS", @@ -4214,6 +4216,15 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/mcis.TbMcisDynamicReq" } + }, + { + "enum": [ + "hold" + ], + "type": "string", + "description": "Option for MCIS creation", + "name": "option", + "in": "query" } ], "responses": { @@ -8397,6 +8408,9 @@ const docTemplate = `{ "location": { "$ref": "#/definitions/common.Location" }, + "regionId": { + "type": "string" + }, "regionName": { "type": "string" }, diff --git a/src/api/rest/docs/swagger.json b/src/api/rest/docs/swagger.json index c3e7533b..2f04f191 100644 --- a/src/api/rest/docs/swagger.json +++ b/src/api/rest/docs/swagger.json @@ -2014,7 +2014,9 @@ "resume", "reboot", "terminate", - "refine" + "refine", + "continue", + "withdraw" ], "type": "string", "description": "Action to MCIS", @@ -4207,6 +4209,15 @@ "schema": { "$ref": "#/definitions/mcis.TbMcisDynamicReq" } + }, + { + "enum": [ + "hold" + ], + "type": "string", + "description": "Option for MCIS creation", + "name": "option", + "in": "query" } ], "responses": { @@ -8390,6 +8401,9 @@ "location": { "$ref": "#/definitions/common.Location" }, + "regionId": { + "type": "string" + }, "regionName": { "type": "string" }, diff --git a/src/api/rest/docs/swagger.yaml b/src/api/rest/docs/swagger.yaml index 39d44618..3dced2b0 100644 --- a/src/api/rest/docs/swagger.yaml +++ b/src/api/rest/docs/swagger.yaml @@ -158,6 +158,8 @@ definitions: type: string location: $ref: '#/definitions/common.Location' + regionId: + type: string regionName: type: string zones: @@ -4211,6 +4213,8 @@ paths: - reboot - terminate - refine + - continue + - withdraw in: query name: action required: true @@ -5702,6 +5706,12 @@ paths: required: true schema: $ref: '#/definitions/mcis.TbMcisDynamicReq' + - description: Option for MCIS creation + enum: + - hold + in: query + name: option + type: string produces: - application/json responses: diff --git a/src/api/rest/server/mcis/control.go b/src/api/rest/server/mcis/control.go index c55f795f..64b82378 100644 --- a/src/api/rest/server/mcis/control.go +++ b/src/api/rest/server/mcis/control.go @@ -31,7 +31,7 @@ import ( // @Produce json // @Param nsId path string true "Namespace ID" default(ns01) // @Param mcisId path string true "MCIS ID" default(mcis01) -// @Param action query string true "Action to MCIS" Enums(suspend, resume, reboot, terminate, refine) +// @Param action query string true "Action to MCIS" Enums(suspend, resume, reboot, terminate, refine, continue, withdraw) // @Param force query string false "Force control to skip checking controllable status" Enums(false, true) // @Success 200 {object} common.SimpleMsg // @Failure 404 {object} common.SimpleMsg @@ -53,7 +53,7 @@ func RestGetControlMcis(c echo.Context) error { } returnObj := common.SimpleMsg{} - if action == "suspend" || action == "resume" || action == "reboot" || action == "terminate" || action == "refine" { + if action == "suspend" || action == "resume" || action == "reboot" || action == "terminate" || action == "refine" || action == "continue" || action == "withdraw" { resultString, err := mcis.HandleMcisAction(nsId, mcisId, action, forceOption) if err != nil { @@ -63,7 +63,7 @@ func RestGetControlMcis(c echo.Context) error { return common.EndRequestWithLog(c, reqID, err, returnObj) } else { - err := fmt.Errorf("'action' should be one of these: suspend, resume, reboot, terminate, refine") + err := fmt.Errorf("'action' should be one of these: suspend, resume, reboot, terminate, refine, continue, withdraw") return common.EndRequestWithLog(c, reqID, err, returnObj) } } diff --git a/src/api/rest/server/mcis/provisioning.go b/src/api/rest/server/mcis/provisioning.go index 5fb64971..bb3f39e1 100644 --- a/src/api/rest/server/mcis/provisioning.go +++ b/src/api/rest/server/mcis/provisioning.go @@ -115,6 +115,7 @@ func RestPostSystemMcis(c echo.Context) error { // @Produce json // @Param nsId path string true "Namespace ID" default(ns01) // @Param mcisReq body TbMcisDynamicReq true "Request body to provision MCIS dynamically" +// @Param option query string false "Option for MCIS creation" Enums(hold) // @Success 200 {object} TbMcisInfo // @Failure 404 {object} common.SimpleMsg // @Failure 500 {object} common.SimpleMsg @@ -125,13 +126,14 @@ func RestPostMcisDynamic(c echo.Context) error { return c.JSON(http.StatusBadRequest, map[string]string{"message": idErr.Error()}) } nsId := c.Param("nsId") + option := c.QueryParam("option") req := &mcis.TbMcisDynamicReq{} if err := c.Bind(req); err != nil { return common.EndRequestWithLog(c, reqID, err, nil) } - result, err := mcis.CreateMcisDynamic(nsId, req) + result, err := mcis.CreateMcisDynamic(nsId, req, option) return common.EndRequestWithLog(c, reqID, err, result) } diff --git a/src/core/common/config.go b/src/core/common/config.go index c08465f4..3d6aed4c 100644 --- a/src/core/common/config.go +++ b/src/core/common/config.go @@ -42,6 +42,7 @@ type CSPDetail struct { // RegionDetail is structure for region information type RegionDetail struct { + RegionId string `mapstructure:"id" json:"regionId"` RegionName string `mapstructure:"regionName" json:"regionName"` Description string `mapstructure:"description" json:"description"` Location Location `mapstructure:"location" json:"location"` @@ -73,6 +74,10 @@ func AdjustKeysToLowercase(cloudInfo *CloudInfo) { for regionKey, regionDetail := range cspDetail.Regions { lowerRegionKey := strings.ToLower(regionKey) regionDetail.RegionName = lowerRegionKey + // keep the original regionId if it is not empty (some CSP uses regionName case-sensitive) + if regionDetail.RegionId == "" { + regionDetail.RegionId = regionKey + } newRegions[lowerRegionKey] = regionDetail } cspDetail.Regions = newRegions diff --git a/src/core/common/utility.go b/src/core/common/utility.go index 62afb721..dd336931 100644 --- a/src/core/common/utility.go +++ b/src/core/common/utility.go @@ -596,14 +596,15 @@ func RegisterRegionZone(providerName string, regionName string) error { // register representative regionZone (region only) requestBody.RegionName = providerName + "-" + regionName keyValueInfoList := []KeyValue{} + if len(RuntimeCloudInfo.CSPs[providerName].Regions[regionName].Zones) > 0 { keyValueInfoList = []KeyValue{ - {Key: "Region", Value: regionName}, + {Key: "Region", Value: RuntimeCloudInfo.CSPs[providerName].Regions[regionName].RegionId}, {Key: "Zone", Value: RuntimeCloudInfo.CSPs[providerName].Regions[regionName].Zones[0]}, } } else { keyValueInfoList = []KeyValue{ - {Key: "Region", Value: regionName}, + {Key: "Region", Value: RuntimeCloudInfo.CSPs[providerName].Regions[regionName].RegionId}, {Key: "Zone", Value: "N/A"}, } } @@ -629,7 +630,7 @@ func RegisterRegionZone(providerName string, regionName string) error { for _, zoneName := range RuntimeCloudInfo.CSPs[providerName].Regions[regionName].Zones { requestBody.RegionName = providerName + "-" + regionName + "-" + zoneName keyValueInfoList := []KeyValue{ - {Key: "Region", Value: regionName}, + {Key: "Region", Value: RuntimeCloudInfo.CSPs[providerName].Regions[regionName].RegionId}, {Key: "Zone", Value: zoneName}, } requestBody.AvailableZoneList = RuntimeCloudInfo.CSPs[providerName].Regions[regionName].Zones diff --git a/src/core/mcir/common.go b/src/core/mcir/common.go index 1f376fcb..0d1644ec 100644 --- a/src/core/mcir/common.go +++ b/src/core/mcir/common.go @@ -1651,12 +1651,12 @@ func LoadCommonResource() (common.IdList, error) { // Update registered Spec object with Cost info costPerHour, err2 := strconv.ParseFloat(strings.ReplaceAll(row[3], " ", ""), 32) if err2 != nil { - log.Error().Err(err2).Msg("Not valid CostPerHour value in the asset") + log.Error().Msgf("Not valid CostPerHour value in the asset: %s", specInfoId) costPerHour = 99999999.9 } evaluationScore01, err2 := strconv.ParseFloat(strings.ReplaceAll(row[4], " ", ""), 32) if err2 != nil { - log.Error().Err(err2).Msg("Not valid evaluationScore01 value in the asset") + log.Error().Msgf("Not valid evaluationScore01 value in the asset: %s", specInfoId) evaluationScore01 = -99.9 } specUpdateRequest := @@ -1912,12 +1912,12 @@ func LoadDefaultResource(nsId string, resType string, connectionName string) err common.PrintJsonPretty(reqTmp) - resultInfo, err := CreateSshKey(nsId, &reqTmp, "") + _, err := CreateSshKey(nsId, &reqTmp, "") if err != nil { log.Error().Err(err).Msg("Failed to create SshKey") return err } - common.PrintJsonPretty(resultInfo) + // common.PrintJsonPretty(resultInfo) } else { return errors.New("Not valid option (provide sg, sshkey, vnet, or all)") } @@ -1981,9 +1981,8 @@ func ToNamingRuleCompatible(rawName string) string { // UpdateResourceObject is func to update the resource object func UpdateResourceObject(nsId string, resourceType string, resourceObject interface{}) { resourceId, err := GetIdFromStruct(resourceObject) - fmt.Printf("in UpdateResourceObject; extracted resourceId: %s \n", resourceId) // for debug if resourceId == "" || err != nil { - fmt.Printf("in UpdateResourceObject; failed to extract resourceId. \n") // for debug + log.Debug().Msgf("in UpdateResourceObject; failed to extract resourceId") // for debug return } diff --git a/src/core/mcir/spec.go b/src/core/mcir/spec.go index b382c6e8..4614e566 100644 --- a/src/core/mcir/spec.go +++ b/src/core/mcir/spec.go @@ -493,7 +493,7 @@ func FilterSpecsByRange(nsId string, filter FilterSpecsByRangeRequest) ([]TbSpec // Convert the first letter of the field name to lowercase to match typical database column naming conventions dbFieldName := strings.ToLower(field.Name[:1]) + field.Name[1:] - log.Info().Msgf("Field: %s, Value: %v", dbFieldName, value) + log.Debug().Msgf("Field: %s, Value: %v", dbFieldName, value) if value.Kind() == reflect.Struct { // Handle range filters like VCPU, MemoryGiB, etc. diff --git a/src/core/mcis/control.go b/src/core/mcis/control.go index 79bdc525..bc1f329b 100644 --- a/src/core/mcis/control.go +++ b/src/core/mcis/control.go @@ -124,6 +124,20 @@ func HandleMcisAction(nsId string, mcisId string, action string, force bool) (st return "Terminated the MCIS", nil + } else if action == "continue" { + log.Debug().Msg("[continue MCIS provisioning]") + key := common.GenMcisKey(nsId, mcisId, "") + holdingMcisMap.Store(key, action) + + return "Continue the holding MCIS", nil + + } else if action == "withdraw" { + log.Debug().Msg("[withdraw MCIS provisioning]") + key := common.GenMcisKey(nsId, mcisId, "") + holdingMcisMap.Store(key, action) + + return "Withdraw the holding MCIS", nil + } else if action == "refine" { // refine delete VMs in StatusFailed or StatusUndefined log.Debug().Msg("[refine MCIS]") diff --git a/src/core/mcis/nlb.go b/src/core/mcis/nlb.go index 397ccfb3..9102b842 100644 --- a/src/core/mcis/nlb.go +++ b/src/core/mcis/nlb.go @@ -339,7 +339,7 @@ func CreateMcSwNlb(nsId string, mcisId string, req *TbNLBReq, option string) (Mc vmDynamicReq := TbVmDynamicReq{Name: vmGroupName, CommonSpec: commonSpec, CommonImage: commonImage, SubGroupSize: subGroupSize} mcisDynamicReq.Vm = append(mcisDynamicReq.Vm, vmDynamicReq) - mcisInfo, err := CreateMcisDynamic(nsId, &mcisDynamicReq) + mcisInfo, err := CreateMcisDynamic(nsId, &mcisDynamicReq, "") if err != nil { log.Error().Err(err).Msg("") return emptyObj, err diff --git a/src/core/mcis/provisioning.go b/src/core/mcis/provisioning.go index b09f2823..b84f1f54 100644 --- a/src/core/mcis/provisioning.go +++ b/src/core/mcis/provisioning.go @@ -510,6 +510,8 @@ type TbVmRecommendInfo struct { PlacementParam []common.KeyValue `json:"placementParam"` } +var holdingMcisMap sync.Map + // MCIS and VM Provisioning // CreateMcisVm is func to post (create) McisVm @@ -957,6 +959,32 @@ func CreateMcis(nsId string, req *TbMcisReq, option string) (*TbMcisInfo, error) } } + // hold option will hold the MCIS creation process until the user releases it. + if option == "hold" { + key := common.GenMcisKey(nsId, mcisId, "") + holdingMcisMap.Store(key, "holding") + for { + value, ok := holdingMcisMap.Load(key) + if !ok { + break + } + if value == "continue" { + holdingMcisMap.Delete(key) + break + } else if value == "withdraw" { + holdingMcisMap.Delete(key) + DelMcis(nsId, mcisId, "force") + err := fmt.Errorf("Withdrawed MCIS creation") + log.Error().Err(err).Msg("") + return nil, err + } + + log.Info().Msgf("MCIS: %s (holding)", key) + time.Sleep(5 * time.Second) + } + option = "create" + } + //goroutin var wg sync.WaitGroup @@ -1222,11 +1250,11 @@ func CreateSystemMcisDynamic(option string) (*TbMcisInfo, error) { return nil, err } - return CreateMcisDynamic(nsId, req) + return CreateMcisDynamic(nsId, req, "") } // CreateMcisDynamic is func to create MCIS obeject and deploy requested VMs in a dynamic way -func CreateMcisDynamic(nsId string, req *TbMcisDynamicReq) (*TbMcisInfo, error) { +func CreateMcisDynamic(nsId string, req *TbMcisDynamicReq, deployOption string) (*TbMcisInfo, error) { mcisReq := TbMcisReq{} mcisReq.Name = req.Name @@ -1290,6 +1318,9 @@ func CreateMcisDynamic(nsId string, req *TbMcisDynamicReq) (*TbMcisInfo, error) // Run create MCIS with the generated MCIS request (option != register) option := "create" + if deployOption == "hold" { + option = "hold" + } return CreateMcis(nsId, &mcisReq, option) } @@ -1643,6 +1674,7 @@ func CreateVm(nsId string, mcisId string, vmInfoData *TbVmInfo, option string) e // Try lookup customImage requestBody.ReqInfo.ImageName, err = common.GetCspResourceId(nsId, common.StrCustomImage, vmInfoData.ImageId) if requestBody.ReqInfo.ImageName == "" || err != nil { + log.Warn().Msgf("Not found the Image: %s in nsId: %s, find it from SystemCommonNs", vmInfoData.ImageId, nsId) errAgg := err.Error() // If customImage doesn't exist, then try lookup image requestBody.ReqInfo.ImageName, err = common.GetCspResourceId(nsId, common.StrImage, vmInfoData.ImageId) @@ -1668,7 +1700,7 @@ func CreateVm(nsId string, mcisId string, vmInfoData *TbVmInfo, option string) e requestBody.ReqInfo.VMSpecName, err = common.GetCspResourceId(nsId, common.StrSpec, vmInfoData.SpecId) if requestBody.ReqInfo.VMSpecName == "" || err != nil { - log.Warn().Msg(err.Error()) + log.Warn().Msgf("Not found the Spec: %s in nsId: %s, find it from SystemCommonNs", vmInfoData.SpecId, nsId) errAgg := err.Error() // If cannot find the resource, use common resource requestBody.ReqInfo.VMSpecName, err = common.GetCspResourceId(common.SystemCommonNs, common.StrSpec, vmInfoData.SpecId) diff --git a/src/main.go b/src/main.go index 6cdd7659..08add54e 100644 --- a/src/main.go +++ b/src/main.go @@ -181,7 +181,7 @@ func setConfig() { for attempt < maxAttempts { if common.CheckSpiderReady() == nil { - log.Info().Msg("CB-Spider is now ready.") + log.Info().Msg("CB-Spider is now ready. Initializing CB-Tumblebug...") break } log.Info().Msgf("CB-Spider at %s is not ready. Attempt %d/%d", common.SpiderRestUrl, attempt+1, maxAttempts)