diff --git a/src/api/rest/docs/docs.go b/src/api/rest/docs/docs.go index 2db09c9a9..b5ef2b5f0 100644 --- a/src/api/rest/docs/docs.go +++ b/src/api/rest/docs/docs.go @@ -3290,6 +3290,65 @@ const docTemplate = `{ } } }, + "/ns/{nsId}/mcis/{mcisId}/site": { + "get": { + "description": "Get sites in MCIS", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[VPN] Sites in MCIS (under development)" + ], + "summary": "Get sites in MCIS", + "parameters": [ + { + "type": "string", + "default": "ns01", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "mcis01", + "description": "MCIS ID", + "name": "mcisId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SitesInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + } + } + } + }, "/ns/{nsId}/mcis/{mcisId}/subgroup": { "get": { "description": "List SubGroup IDs in a specified MCIS", @@ -12553,6 +12612,65 @@ const docTemplate = `{ } } }, + "model.SiteDetail": { + "type": "object", + "properties": { + "csp": { + "type": "string", + "example": "aws" + }, + "gatewaySubnetCidr": { + "type": "string", + "example": "xxx.xxx.xxx.xxx/xx" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "resourceGroup": { + "type": "string", + "example": "rg-xxxxx" + }, + "subnet": { + "type": "string", + "example": "subnet-xxxxx" + }, + "vnet": { + "type": "string", + "example": "vpc-xxxxx" + }, + "zone": { + "type": "string", + "example": "ap-northeast-2a" + } + } + }, + "model.SitesInfo": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 3 + }, + "mcisId": { + "type": "string", + "example": "mcis-01" + }, + "nsId": { + "type": "string", + "example": "ns-01" + }, + "sites": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/model.SiteDetail" + } + } + } + } + }, "model.TfVarsGcpAwsVpnTunnel": { "type": "object", "required": [ diff --git a/src/api/rest/docs/swagger.json b/src/api/rest/docs/swagger.json index 502f47612..450404214 100644 --- a/src/api/rest/docs/swagger.json +++ b/src/api/rest/docs/swagger.json @@ -3283,6 +3283,65 @@ } } }, + "/ns/{nsId}/mcis/{mcisId}/site": { + "get": { + "description": "Get sites in MCIS", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[VPN] Sites in MCIS (under development)" + ], + "summary": "Get sites in MCIS", + "parameters": [ + { + "type": "string", + "default": "ns01", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "mcis01", + "description": "MCIS ID", + "name": "mcisId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SitesInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/common.SimpleMsg" + } + } + } + } + }, "/ns/{nsId}/mcis/{mcisId}/subgroup": { "get": { "description": "List SubGroup IDs in a specified MCIS", @@ -12546,6 +12605,65 @@ } } }, + "model.SiteDetail": { + "type": "object", + "properties": { + "csp": { + "type": "string", + "example": "aws" + }, + "gatewaySubnetCidr": { + "type": "string", + "example": "xxx.xxx.xxx.xxx/xx" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "resourceGroup": { + "type": "string", + "example": "rg-xxxxx" + }, + "subnet": { + "type": "string", + "example": "subnet-xxxxx" + }, + "vnet": { + "type": "string", + "example": "vpc-xxxxx" + }, + "zone": { + "type": "string", + "example": "ap-northeast-2a" + } + } + }, + "model.SitesInfo": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 3 + }, + "mcisId": { + "type": "string", + "example": "mcis-01" + }, + "nsId": { + "type": "string", + "example": "ns-01" + }, + "sites": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/model.SiteDetail" + } + } + } + } + }, "model.TfVarsGcpAwsVpnTunnel": { "type": "object", "required": [ diff --git a/src/api/rest/docs/swagger.yaml b/src/api/rest/docs/swagger.yaml index 0ae011860..cf41854fc 100644 --- a/src/api/rest/docs/swagger.yaml +++ b/src/api/rest/docs/swagger.yaml @@ -2794,6 +2794,48 @@ definitions: tfVars: $ref: '#/definitions/model.TfVarsGcpAwsVpnTunnel' type: object + model.SiteDetail: + properties: + csp: + example: aws + type: string + gatewaySubnetCidr: + example: xxx.xxx.xxx.xxx/xx + type: string + region: + example: ap-northeast-2 + type: string + resourceGroup: + example: rg-xxxxx + type: string + subnet: + example: subnet-xxxxx + type: string + vnet: + example: vpc-xxxxx + type: string + zone: + example: ap-northeast-2a + type: string + type: object + model.SitesInfo: + properties: + count: + example: 3 + type: integer + mcisId: + example: mcis-01 + type: string + nsId: + example: ns-01 + type: string + sites: + additionalProperties: + additionalProperties: + $ref: '#/definitions/model.SiteDetail' + type: object + type: object + type: object model.TfVarsGcpAwsVpnTunnel: properties: aws-region: @@ -5133,6 +5175,46 @@ paths: summary: Add VMs to NLB tags: - '[Infra resource] NLB management (for developer)' + /ns/{nsId}/mcis/{mcisId}/site: + get: + consumes: + - application/json + description: Get sites in MCIS + parameters: + - default: ns01 + description: Namespace ID + in: path + name: nsId + required: true + type: string + - default: mcis01 + description: MCIS ID + in: path + name: mcisId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.SitesInfo' + "400": + description: Bad Request + schema: + $ref: '#/definitions/common.SimpleMsg' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/common.SimpleMsg' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/common.SimpleMsg' + summary: Get sites in MCIS + tags: + - '[VPN] Sites in MCIS (under development)' /ns/{nsId}/mcis/{mcisId}/subgroup: get: consumes: diff --git a/src/api/rest/server/mcis/network.go b/src/api/rest/server/mcis/network.go index 26d5b1f90..54c21daee 100644 --- a/src/api/rest/server/mcis/network.go +++ b/src/api/rest/server/mcis/network.go @@ -23,11 +23,159 @@ import ( "github.com/cloud-barista/cb-tumblebug/src/api/rest/server/model" "github.com/cloud-barista/cb-tumblebug/src/core/common" + "github.com/cloud-barista/cb-tumblebug/src/core/common/netutil" + "github.com/cloud-barista/cb-tumblebug/src/core/mcir" + "github.com/cloud-barista/cb-tumblebug/src/core/mcis" "github.com/go-resty/resty/v2" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) +// RestGetSitesInMcis godoc +// @Summary Get sites in MCIS +// @Description Get sites in MCIS +// @Tags [VPN] Sites in MCIS (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(ns01) +// @Param mcisId path string true "MCIS ID" default(mcis01) +// @Success 200 {object} model.SitesInfo "OK" +// @Failure 400 {object} common.SimpleMsg "Bad Request" +// @Failure 500 {object} common.SimpleMsg "Internal Server Error" +// @Failure 503 {object} common.SimpleMsg "Service Unavailable" +// @Router /ns/{nsId}/mcis/{mcisId}/site [get] +func RestGetSitesInMcis(c echo.Context) error { + + nsId := c.Param("nsId") + if nsId == "" { + err := fmt.Errorf("invalid request, namespace ID (nsId: %s) is required", nsId) + log.Warn().Msg(err.Error()) + res := common.SimpleMsg{ + Message: err.Error(), + } + + return c.JSON(http.StatusBadRequest, res) + } + + mcisId := c.Param("mcisId") + if mcisId == "" { + err := fmt.Errorf("invalid request, MCIS ID (mcisId: %s) is required", mcisId) + log.Warn().Msg(err.Error()) + res := common.SimpleMsg{ + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + SitesInfo, err := ExtractSitesInfoFromMcisInfo(nsId, mcisId) + if err != nil { + log.Err(err).Msg("") + res := common.SimpleMsg{ + Message: err.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + return c.JSON(http.StatusOK, SitesInfo) +} + +func ExtractSitesInfoFromMcisInfo(nsId, mcisId string) (*model.SitesInfo, error) { + // Get MCIS info + mcisInfo, err := mcis.GetMcisInfo(nsId, mcisId) + if err != nil { + log.Err(err).Msg("") + return nil, err + } + + // Newly create the SitesInfo structure + sitesInfo := model.NewSiteInfo(nsId, mcisId) + + for _, vm := range mcisInfo.Vm { + providerName := vm.ConnectionConfig.ProviderName + if providerName == "" { + log.Warn().Msgf("Provider name is empty for VM ID: %s", vm.Id) + continue + } + + vNetId := vm.VNetId + if vNetId == "" { + log.Warn().Msgf("VNet ID is empty for VM ID: %s", vm.Id) + continue + } + + // Add or update the site detail in the map based on the provider + providerName = strings.ToLower(providerName) + + // Use vNetId as the site ID + if _, exists := sitesInfo.Sites[providerName][vm.VNetId]; !exists { + var site = model.SiteDetail{} + switch providerName { + case "aws": + site.CSP = vm.ConnectionConfig.ProviderName + site.Region = vm.CspViewVmDetail.Region.Region + + // Note - It must be updated. + // Temporarily use the subnet ID to which the VM is attached/deployed + // Set VNet and subnet IDs + site.VNet = vm.CspViewVmDetail.SubnetIID.SystemId + site.Subnet = vm.CspViewVmDetail.SubnetIID.SystemId + + case "azure": + site.CSP = vm.ConnectionConfig.ProviderName + site.Region = vm.CspViewVmDetail.Region.Region + + // Parse vNet and resource group names + parts := strings.Split(vm.CspViewVmDetail.VpcIID.SystemId, "/") + log.Debug().Msgf("parts: %+v", parts) + ParsedResourceGroupName := parts[4] + ParsedVirtualNetworkName := parts[8] + + // Set VNet and resource group names + site.VNet = ParsedVirtualNetworkName + site.ResourceGroup = ParsedResourceGroupName + + // Get vNet info + resourceType := "vNet" + resourceId := vm.VNetId + result, err := mcir.GetResource(nsId, resourceType, resourceId) + if err != nil { + log.Warn().Msgf("Failed to get the VNet info for ID: %s", resourceId) + continue + } + vNetInfo := result.(mcir.TbVNetInfo) + + // Get the last subnet CIDR block + subnetCount := len(vNetInfo.SubnetInfoList) + lastSubnet := vNetInfo.SubnetInfoList[subnetCount-1] + lastSubnetCidr := lastSubnet.IPv4_CIDR + + // (Currently unsafe) Calculate the next subnet CIDR block + nextCidr, err := netutil.NextSubnet(lastSubnetCidr, vNetInfo.CidrBlock) + if err != nil { + log.Warn().Msgf("Failed to get the next subnet CIDR") + } + + // Set the site detail + site.GatewaySubnetCidr = nextCidr + + case "gcp": + site.CSP = vm.ConnectionConfig.ProviderName + site.Region = vm.CspViewVmDetail.Region.Region + site.VNet = vm.CspViewVmDetail.VpcIID.SystemId + default: + log.Warn().Msgf("Unsupported provider name: %s", providerName) + } + + if site != (model.SiteDetail{}) { + sitesInfo.Sites[providerName][vm.VNetId] = site + sitesInfo.Count++ + } + } + } + + return sitesInfo, nil +} + // RestPostVpnGcpToAws godoc // @Summary Create VPN tunnels between GCP and AWS (Note - Streaming JSON response) // @Description Create VPN tunnels between GCP and AWS (Note - Streaming JSON response) diff --git a/src/api/rest/server/model/network.go b/src/api/rest/server/model/network.go index fee2bd8ce..454b50a8f 100644 --- a/src/api/rest/server/model/network.go +++ b/src/api/rest/server/model/network.go @@ -14,20 +14,46 @@ limitations under the License. // Package mcis is to handle REST API for mcis package model -// type ResponseText struct { -// Success bool `json:"success" example:"true"` -// Text string `json:"text" example:"Any text"` -// } +var ProviderNames = map[string]string{ + "AWS": "aws", + "Azure": "azure", + "GCP": "gcp", +} -// type ResponseList struct { -// Success bool `json:"success" example:"true"` -// List []interface{} `json:"list"` -// } +// SiteDetail struct represents the structure for detailed site information +type SiteDetail struct { + CSP string `json:"csp" example:"aws"` + Region string `json:"region" example:"ap-northeast-2"` + Zone string `json:"zone,omitempty" example:"ap-northeast-2a"` + VNet string `json:"vnet" example:"vpc-xxxxx"` + Subnet string `json:"subnet,omitempty" example:"subnet-xxxxx"` + GatewaySubnetCidr string `json:"gatewaySubnetCidr,omitempty" example:"xxx.xxx.xxx.xxx/xx"` + ResourceGroup string `json:"resourceGroup,omitempty" example:"rg-xxxxx"` +} + +// SitesInfo struct represents the overall site information including namespace and MCIS ID +type SitesInfo struct { + NsId string `json:"nsId" example:"ns-01"` + McisId string `json:"mcisId" example:"mcis-01"` + Count int `json:"count" example:"3"` + Sites map[string]map[string]SiteDetail `json:"sites"` +} + +func NewSiteInfo(nsId, mcisId string) *SitesInfo { + siteInfo := &SitesInfo{ + NsId: nsId, + McisId: mcisId, + Count: 0, + Sites: make(map[string]map[string]SiteDetail), + } + + for _, providerName := range ProviderNames { + siteInfo.Sites[providerName] = make(map[string]SiteDetail) + } + + return siteInfo +} -// type ResponseObject struct { -// Success bool `json:"success" example:"true"` -// Object map[string]interface{} `json:"object"` -// } type Response struct { Success bool `json:"success" example:"true"` Text string `json:"text" example:"Any text"` diff --git a/src/api/rest/server/server.go b/src/api/rest/server/server.go index 4e3fb1952..86976040e 100644 --- a/src/api/rest/server/server.go +++ b/src/api/rest/server/server.go @@ -280,6 +280,9 @@ func RunServer(port string) { g.POST("/:nsId/benchmarkAll/mcis/:mcisId", rest_mcis.RestGetAllBenchmark) g.GET("/:nsId/benchmarkLatency/mcis/:mcisId", rest_mcis.RestGetBenchmarkLatency) + // VPN Sites info + g.GET("/:nsId/mcis/:mcisId/site", rest_mcis.RestGetSitesInMcis) + // VPN Management g.POST("/:nsId/mcis/:mcisId/vpn/:vpnId/gcp-aws", rest_mcis.RestPostVpnGcpToAws) g.GET("/:nsId/mcis/:mcisId/vpn/:vpnId/gcp-aws", rest_mcis.RestGetVpnGcpToAws)