diff --git a/src/api/rest/server/mcis/plan.go b/src/api/rest/server/mcis/plan.go new file mode 100644 index 000000000..5996becfd --- /dev/null +++ b/src/api/rest/server/mcis/plan.go @@ -0,0 +1,42 @@ +package mcis + +import ( + "net/http" + + "github.com/cloud-barista/cb-tumblebug/src/core/common" + "github.com/cloud-barista/cb-tumblebug/src/core/mcis" + "github.com/labstack/echo/v4" +) + +// RestRecommendVm godoc +// @Summary RestRecommendVm specs by range +// @Description RestRecommendVm specs by range +// @Tags [MCIS] Provisioning management +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" +// @Param deploymentPlan body mcis.DeploymentPlan false "RestRecommendVm for range-filtering specs" +// @Success 200 {object} []mcir.TbSpecInfo +// @Failure 404 {object} common.SimpleMsg +// @Failure 500 {object} common.SimpleMsg +// @Router /ns/{nsId}/testRecommendVm [post] +func RestRecommendVm(c echo.Context) error { + + nsId := c.Param("nsId") + + u := &mcis.DeploymentPlan{} + if err := c.Bind(u); err != nil { + return err + } + + content, err := mcis.RecommendVm(nsId, *u) + + if err != nil { + common.CBLog.Error(err) + return c.JSONBlob(http.StatusNotFound, []byte(err.Error())) + } + + // result := RestFilterSpecsResponse{} + // result.Spec = content + return c.JSON(http.StatusOK, &content) +} diff --git a/src/api/rest/server/server.go b/src/api/rest/server/server.go index 090ead4b9..c0561ab8c 100644 --- a/src/api/rest/server/server.go +++ b/src/api/rest/server/server.go @@ -153,6 +153,9 @@ func ApiServer() { //g.DELETE("/:nsId/mcis/:mcisId/vm", rest_mcis.RestDelAllMcisVm) g.POST("/:nsId/mcis/recommend", rest_mcis.RestPostMcisRecommand) + + g.POST("/:nsId/testRecommendVm", rest_mcis.RestRecommendVm) + g.POST("/:nsId/cmd/mcis/:mcisId", rest_mcis.RestPostCmdMcis) g.POST("/:nsId/cmd/mcis/:mcisId/vm/:vmId", rest_mcis.RestPostCmdMcisVm) g.POST("/:nsId/install/mcis/:mcisId", rest_mcis.RestPostInstallAgentToMcis) diff --git a/src/core/mcis/control.go b/src/core/mcis/control.go index 46a5359f0..bd59e9deb 100644 --- a/src/core/mcis/control.go +++ b/src/core/mcis/control.go @@ -4103,10 +4103,23 @@ func GetVmTemplate(nsId string, mcisId string, algo string) (TbVmInfo, error) { } +// GetCloudLocation. (need error handling) func GetCloudLocation(cloudType string, nativeRegion string) GeoLocation { location := GeoLocation{} + if cloudType == "" || nativeRegion == "" { + + // need error handling instead of assigning default value + location.CloudType = "ufc" + location.NativeRegion = "ufc" + location.BriefAddr = "South Korea (Seoul)" + location.Latitude = "37.4767" + location.Longitude = "126.8841" + + return location + } + key := "/cloudtype/" + cloudType + "/region/" + nativeRegion fmt.Printf("[GetCloudLocation] KEY: %+v\n", key) @@ -4146,11 +4159,11 @@ func GetCloudLocation(cloudType string, nativeRegion string) GeoLocation { } fmt.Println() } - } - keyValue, err = common.CBStore.Get(key) - if err != nil { - common.CBLog.Error(err) - return location + keyValue, err = common.CBStore.Get(key) + if err != nil { + common.CBLog.Error(err) + return location + } } if keyValue != nil { diff --git a/src/core/mcis/plan.go b/src/core/mcis/plan.go new file mode 100644 index 000000000..c619393c1 --- /dev/null +++ b/src/core/mcis/plan.go @@ -0,0 +1,286 @@ +package mcis + +import ( + "fmt" + "math" + "sort" + "strconv" + "strings" + + "github.com/cloud-barista/cb-tumblebug/src/core/common" + "github.com/cloud-barista/cb-tumblebug/src/core/mcir" +) + +// DeploymentPlan is struct for . +type DeploymentPlan struct { + Filter FilterInfo `json:"filter"` + Priority PriorityInfo `json:"priority"` + Limit string `json:"limit"` +} + +// FilterInfo is struct for . +type FilterInfo struct { + Policy []FilterCondition `json:"policy"` +} + +// FilterCondition is struct for . +type FilterCondition struct { + Metric string `json:"metric"` + Condition []Operation `json:"condition"` +} + +// Operation is struct for . +type Operation struct { + Operator string `json:"operator" ` // <, <=, >, >=, ... + Operand string `json:"operand"` // 10, 70, 80, 98, ... +} + +// PriorityInfo is struct for . +type PriorityInfo struct { + Policy []PriorityCondition `json:"policy"` +} + +// FilterCondition is struct for . +type PriorityCondition struct { + Metric string `json:"metric"` // location + Weight string `json:"weight"` // 0.3 + Parameter []ParameterKeyVal `json:"parameter"` +} + +// Operation is struct for . +type ParameterKeyVal struct { + Key string `json:"key" ` // coordinate + Val []string `json:"val"` // [{Latitude,Longitude},{12,543},{66,33},{31,433}] +} + +/// + +//// Info manage for MCIS recommendation +func RecommendVm(nsId string, plan DeploymentPlan) ([]mcir.TbSpecInfo, error) { + + fmt.Println("RecommendVm") + + // Filtering first + + u := &mcir.FilterSpecsByRangeRequest{} + + // veryLargeValue := float32(math.MaxFloat32) + // verySmallValue := float32(0) + + // Filtering + fmt.Println("[Filtering specs]") + + for _, v := range plan.Filter.Policy { + metric := v.Metric + conditions := v.Condition + for _, condition := range conditions { + + operand64, err := strconv.ParseFloat(condition.Operand, 32) + operand := float32(operand64) + if err != nil { + common.CBLog.Error(err) + return []mcir.TbSpecInfo{}, err + } + + switch condition.Operator { + case "<=": + + switch metric { + case "num_vCPU": + u.Num_vCPU.Max = operand + case "mem_GiB": + u.Mem_GiB.Max = operand + case "Cost_per_hour": + u.Cost_per_hour.Max = operand + default: + fmt.Println("[Checking] Not available metric " + metric) + } + + case ">=": + + switch metric { + case "num_vCPU": + u.Num_vCPU.Min = operand + case "mem_GiB": + u.Mem_GiB.Min = operand + case "Cost_per_hour": + u.Cost_per_hour.Min = operand + default: + fmt.Println("[Checking] Not available metric " + metric) + } + + case "==": + + switch metric { + case "num_vCPU": + u.Num_vCPU.Max = operand + u.Num_vCPU.Min = operand + case "mem_GiB": + u.Mem_GiB.Max = operand + u.Mem_GiB.Min = operand + case "Cost_per_hour": + u.Cost_per_hour.Max = operand + u.Cost_per_hour.Min = operand + default: + fmt.Println("[Checking] Not available metric " + metric) + } + } + } + } + + filteredSpecs, err := mcir.FilterSpecsByRange(nsId, *u) + + if err != nil { + common.CBLog.Error(err) + return []mcir.TbSpecInfo{}, err + } + + // Prioritizing + fmt.Println("[Prioritizing specs]") + prioritySpecs := []mcir.TbSpecInfo{} + + for _, v := range plan.Priority.Policy { + metric := v.Metric + + switch metric { + case "location": + prioritySpecs, err = RecommendVmLocation(nsId, &filteredSpecs, &v.Parameter) + case "latency": + // + case "cost": + // + default: + // fmt.Println("[Checking] Not available metric " + metric) + } + + } + + return prioritySpecs, nil + +} + +// RecommendVmLocation func prioritize specs based on given location +func RecommendVmLocation(nsId string, specList *[]mcir.TbSpecInfo, param *[]ParameterKeyVal) ([]mcir.TbSpecInfo, error) { + + result := []mcir.TbSpecInfo{} + + for _, v := range *param { + + switch v.Key { + case "coordinateClose": + // + coordinateStr := v.Val[0] + + slice := strings.Split(coordinateStr, ",") + latitude, err := strconv.ParseFloat(slice[0], 32) + if err != nil { + common.CBLog.Error(err) + return []mcir.TbSpecInfo{}, err + } + longitude, err := strconv.ParseFloat(slice[1], 32) + if err != nil { + common.CBLog.Error(err) + return []mcir.TbSpecInfo{}, err + } + + type distanceType struct { + distance float64 + index int + priorityIndex int + } + distances := []distanceType{} + + for i := range *specList { + distances = append(distances, distanceType{}) + distances[i].distance, err = getDistance(latitude, longitude, (*specList)[i].ConnectionName) + if err != nil { + common.CBLog.Error(err) + return []mcir.TbSpecInfo{}, err + } + distances[i].index = i + } + + sort.Slice(distances, func(i, j int) bool { + return distances[i].distance < distances[j].distance + }) + fmt.Printf("\n distances : %v \n", distances) + + priorityCnt := 1 + for i := range distances { + + // priorityIndex++ if two distances are not equal (give the same priorityIndex if two variables are same) + if i != 0 { + if distances[i].distance > distances[i-1].distance { + priorityCnt++ + } + } + distances[i].priorityIndex = priorityCnt + + } + + for i := range *specList { + // update OrderInFilteredResult based on calculated priorityIndex + (*specList)[distances[i].index].OrderInFilteredResult = uint16(distances[i].priorityIndex) + // assign nomalized priorityIdex value to EvaluationScore_01 + (*specList)[distances[i].index].EvaluationScore_01 = float32(1 - (float32(distances[i].priorityIndex) / float32(len(*specList)))) + (*specList)[distances[i].index].EvaluationScore_02 = float32(distances[i].distance) + } + fmt.Printf("\n distances : %v \n", distances) + + //fmt.Printf("\n distances : %v \n", *specList) + + case "coordinateWithin": + // + case "coordinateFair": + // + default: + // fmt.Println("[Checking] Not available metric " + metric) + } + + } + + for i := range *specList { + result = append(result, (*specList)[i]) + //result[i].OrderInFilteredResult = uint16(i + 1) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].OrderInFilteredResult < result[j].OrderInFilteredResult + }) + fmt.Printf("\n result : %v \n", result) + + // updatedSpec, err := mcir.UpdateSpec(nsId, *result) + // content, err = mcir.SortSpecs(*specList, "mem_GiB", "descending") + return result, nil +} + +// getDistance func get geographical distance between given coordinate and connectionConfig +func getDistance(latitude float64, longitude float64, ConnectionName string) (float64, error) { + configTmp, _ := common.GetConnConfig(ConnectionName) + regionTmp, _ := common.GetRegionInfo(configTmp.RegionName) + + nativeRegion := "" + for _, v := range regionTmp.KeyValueInfoList { + if strings.ToLower(v.Key) == "region" || strings.ToLower(v.Key) == "location" { + nativeRegion = v.Value + break + } + } + Location := GetCloudLocation(strings.ToLower(configTmp.ProviderName), strings.ToLower(nativeRegion)) + + cloudLatitude, err := strconv.ParseFloat(Location.Latitude, 32) + if err != nil { + common.CBLog.Error(err) + return 0, err + } + cloudLongitude, err := strconv.ParseFloat(Location.Longitude, 32) + if err != nil { + common.CBLog.Error(err) + return 0, err + } + + first := math.Pow(float64(cloudLatitude-latitude), 2) + second := math.Pow(float64(cloudLongitude-longitude), 2) + return math.Sqrt(first + second), nil + +} diff --git a/src/docs/docs.go b/src/docs/docs.go index f4bf627c5..af1496353 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -3395,6 +3395,61 @@ var doc = `{ } } }, + "/ns/{nsId}/testRecommendVm": { + "post": { + "description": "RestRecommendVm specs by range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[MCIS] Provisioning management" + ], + "summary": "RestRecommendVm specs by range", + "parameters": [ + { + "type": "string", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "RestRecommendVm for range-filtering specs", + "name": "deploymentPlan", + "in": "body", + "schema": { + "$ref": "#/definitions/mcis.DeploymentPlan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/mcir.TbSpecInfo" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + } + } + } + }, "/object": { "get": { "description": "Get value of an object", @@ -4732,6 +4787,45 @@ var doc = `{ } } }, + "mcis.DeploymentPlan": { + "type": "object", + "properties": { + "filter": { + "$ref": "#/definitions/mcis.FilterInfo" + }, + "limit": { + "type": "string" + }, + "priority": { + "$ref": "#/definitions/mcis.PriorityInfo" + } + } + }, + "mcis.FilterCondition": { + "type": "object", + "properties": { + "condition": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.Operation" + } + }, + "metric": { + "type": "string" + } + } + }, + "mcis.FilterInfo": { + "type": "object", + "properties": { + "policy": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.FilterCondition" + } + } + } + }, "mcis.GeoLocation": { "type": "object", "properties": { @@ -4903,6 +4997,35 @@ var doc = `{ } } }, + "mcis.Operation": { + "type": "object", + "properties": { + "operand": { + "description": "10, 70, 80, 98, ...", + "type": "string" + }, + "operator": { + "description": "\u003c, \u003c=, \u003e, \u003e=, ...", + "type": "string" + } + } + }, + "mcis.ParameterKeyVal": { + "type": "object", + "properties": { + "key": { + "description": "coordinate", + "type": "string" + }, + "val": { + "description": "[{Latitude,Longitude},{12,543},{66,33},{31,433}]", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "mcis.Policy": { "type": "object", "properties": { @@ -4917,6 +5040,36 @@ var doc = `{ } } }, + "mcis.PriorityCondition": { + "type": "object", + "properties": { + "metric": { + "description": "location", + "type": "string" + }, + "parameter": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.ParameterKeyVal" + } + }, + "weight": { + "description": "0.3", + "type": "string" + } + } + }, + "mcis.PriorityInfo": { + "type": "object", + "properties": { + "policy": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.PriorityCondition" + } + } + } + }, "mcis.RegionInfo": { "type": "object", "properties": { diff --git a/src/docs/swagger.json b/src/docs/swagger.json index c7a122c2b..dec01844f 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -3380,6 +3380,61 @@ } } }, + "/ns/{nsId}/testRecommendVm": { + "post": { + "description": "RestRecommendVm specs by range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[MCIS] Provisioning management" + ], + "summary": "RestRecommendVm specs by range", + "parameters": [ + { + "type": "string", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "RestRecommendVm for range-filtering specs", + "name": "deploymentPlan", + "in": "body", + "schema": { + "$ref": "#/definitions/mcis.DeploymentPlan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/mcir.TbSpecInfo" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + } + } + } + }, "/object": { "get": { "description": "Get value of an object", @@ -4717,6 +4772,45 @@ } } }, + "mcis.DeploymentPlan": { + "type": "object", + "properties": { + "filter": { + "$ref": "#/definitions/mcis.FilterInfo" + }, + "limit": { + "type": "string" + }, + "priority": { + "$ref": "#/definitions/mcis.PriorityInfo" + } + } + }, + "mcis.FilterCondition": { + "type": "object", + "properties": { + "condition": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.Operation" + } + }, + "metric": { + "type": "string" + } + } + }, + "mcis.FilterInfo": { + "type": "object", + "properties": { + "policy": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.FilterCondition" + } + } + } + }, "mcis.GeoLocation": { "type": "object", "properties": { @@ -4888,6 +4982,35 @@ } } }, + "mcis.Operation": { + "type": "object", + "properties": { + "operand": { + "description": "10, 70, 80, 98, ...", + "type": "string" + }, + "operator": { + "description": "\u003c, \u003c=, \u003e, \u003e=, ...", + "type": "string" + } + } + }, + "mcis.ParameterKeyVal": { + "type": "object", + "properties": { + "key": { + "description": "coordinate", + "type": "string" + }, + "val": { + "description": "[{Latitude,Longitude},{12,543},{66,33},{31,433}]", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "mcis.Policy": { "type": "object", "properties": { @@ -4902,6 +5025,36 @@ } } }, + "mcis.PriorityCondition": { + "type": "object", + "properties": { + "metric": { + "description": "location", + "type": "string" + }, + "parameter": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.ParameterKeyVal" + } + }, + "weight": { + "description": "0.3", + "type": "string" + } + } + }, + "mcis.PriorityInfo": { + "type": "object", + "properties": { + "policy": { + "type": "array", + "items": { + "$ref": "#/definitions/mcis.PriorityCondition" + } + } + } + }, "mcis.RegionInfo": { "type": "object", "properties": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 9c2189cad..7ba141861 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -692,6 +692,31 @@ definitions: $ref: '#/definitions/mcis.BenchmarkInfo' type: array type: object + mcis.DeploymentPlan: + properties: + filter: + $ref: '#/definitions/mcis.FilterInfo' + limit: + type: string + priority: + $ref: '#/definitions/mcis.PriorityInfo' + type: object + mcis.FilterCondition: + properties: + condition: + items: + $ref: '#/definitions/mcis.Operation' + type: array + metric: + type: string + type: object + mcis.FilterInfo: + properties: + policy: + items: + $ref: '#/definitions/mcis.FilterCondition' + type: array + type: object mcis.GeoLocation: properties: briefAddr: @@ -807,6 +832,26 @@ definitions: nsId: type: string type: object + mcis.Operation: + properties: + operand: + description: 10, 70, 80, 98, ... + type: string + operator: + description: <, <=, >, >=, ... + type: string + type: object + mcis.ParameterKeyVal: + properties: + key: + description: coordinate + type: string + val: + description: '[{Latitude,Longitude},{12,543},{66,33},{31,433}]' + items: + type: string + type: array + type: object mcis.Policy: properties: autoAction: @@ -816,6 +861,26 @@ definitions: status: type: string type: object + mcis.PriorityCondition: + properties: + metric: + description: location + type: string + parameter: + items: + $ref: '#/definitions/mcis.ParameterKeyVal' + type: array + weight: + description: "0.3" + type: string + type: object + mcis.PriorityInfo: + properties: + policy: + items: + $ref: '#/definitions/mcis.PriorityCondition' + type: array + type: object mcis.RegionInfo: properties: region: @@ -3487,6 +3552,42 @@ paths: summary: Get VNet tags: - '[MCIR] Network management' + /ns/{nsId}/testRecommendVm: + post: + consumes: + - application/json + description: RestRecommendVm specs by range + parameters: + - description: Namespace ID + in: path + name: nsId + required: true + type: string + - description: RestRecommendVm for range-filtering specs + in: body + name: deploymentPlan + schema: + $ref: '#/definitions/mcis.DeploymentPlan' + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/mcir.TbSpecInfo' + type: array + "404": + description: Not Found + schema: + $ref: '#/definitions/common.SimpleMsg' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/common.SimpleMsg' + summary: RestRecommendVm specs by range + tags: + - '[MCIS] Provisioning management' /object: delete: consumes: