From 171e262e95f5eb4dd06c1fb17078a39901dd4706 Mon Sep 17 00:00:00 2001 From: Yunkon Kim Date: Mon, 14 Oct 2024 22:39:33 +0900 Subject: [PATCH] Enhance for network design and configuration --- src/api/rest/docs/docs.go | 80 ++++++++++++--------- src/api/rest/docs/swagger.json | 80 ++++++++++++--------- src/api/rest/docs/swagger.yaml | 53 ++++++++------ src/core/model/vnet.go | 25 ++++--- src/core/resource/subnet.go | 26 ++++++- src/core/resource/vnet.go | 123 ++++++++++++++++++++------------- 6 files changed, 239 insertions(+), 148 deletions(-) diff --git a/src/api/rest/docs/docs.go b/src/api/rest/docs/docs.go index 5fccdf32c..290f264fe 100644 --- a/src/api/rest/docs/docs.go +++ b/src/api/rest/docs/docs.go @@ -10424,20 +10424,6 @@ const docTemplate = `{ } } }, - "model.CspRegion": { - "type": "object", - "properties": { - "connectionName": { - "type": "string" - }, - "neededVNets": { - "type": "array", - "items": { - "$ref": "#/definitions/model.NeededVNet" - } - } - } - }, "model.CustomImageStatus": { "type": "string", "enum": [ @@ -10915,6 +10901,20 @@ const docTemplate = `{ } } }, + "model.McNetConfigurationDetails": { + "type": "object", + "properties": { + "csp": { + "type": "string" + }, + "regions": { + "type": "array", + "items": { + "$ref": "#/definitions/model.RegionDetails" + } + } + } + }, "model.McNlbInfo": { "type": "object", "properties": { @@ -11190,20 +11190,6 @@ const docTemplate = `{ } } }, - "model.NeededVNet": { - "type": "object", - "properties": { - "subnetCount": { - "type": "integer" - }, - "subnetSize": { - "type": "integer" - }, - "zoneSelectionMethod": { - "type": "string" - } - } - }, "model.NsInfo": { "type": "object", "properties": { @@ -11396,6 +11382,20 @@ const docTemplate = `{ } } }, + "model.RegionDetails": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "vNets": { + "type": "array", + "items": { + "$ref": "#/definitions/model.VNetDetails" + } + } + } + }, "model.RegionInfo": { "type": "object", "properties": { @@ -14431,6 +14431,20 @@ const docTemplate = `{ } } }, + "model.VNetDetails": { + "type": "object", + "properties": { + "hostsPerSubnet": { + "type": "string" + }, + "subnetCount": { + "type": "string" + }, + "useFirstNZones": { + "type": "string" + } + } + }, "model.inspectOverview": { "type": "object", "properties": { @@ -14547,17 +14561,17 @@ const docTemplate = `{ "netutil.RestPostUtilToDesignVNetRequest": { "type": "object", "properties": { - "cspRegions": { + "desiredPrivateNetwork": { + "type": "string" + }, + "mcNetConfigurations": { "type": "array", "items": { - "$ref": "#/definitions/model.CspRegion" + "$ref": "#/definitions/model.McNetConfigurationDetails" } }, "supernettingEnabled": { "type": "string" - }, - "targetPrivateNetwork": { - "type": "string" } } }, diff --git a/src/api/rest/docs/swagger.json b/src/api/rest/docs/swagger.json index 2ace720b3..9da812146 100644 --- a/src/api/rest/docs/swagger.json +++ b/src/api/rest/docs/swagger.json @@ -10417,20 +10417,6 @@ } } }, - "model.CspRegion": { - "type": "object", - "properties": { - "connectionName": { - "type": "string" - }, - "neededVNets": { - "type": "array", - "items": { - "$ref": "#/definitions/model.NeededVNet" - } - } - } - }, "model.CustomImageStatus": { "type": "string", "enum": [ @@ -10908,6 +10894,20 @@ } } }, + "model.McNetConfigurationDetails": { + "type": "object", + "properties": { + "csp": { + "type": "string" + }, + "regions": { + "type": "array", + "items": { + "$ref": "#/definitions/model.RegionDetails" + } + } + } + }, "model.McNlbInfo": { "type": "object", "properties": { @@ -11183,20 +11183,6 @@ } } }, - "model.NeededVNet": { - "type": "object", - "properties": { - "subnetCount": { - "type": "integer" - }, - "subnetSize": { - "type": "integer" - }, - "zoneSelectionMethod": { - "type": "string" - } - } - }, "model.NsInfo": { "type": "object", "properties": { @@ -11389,6 +11375,20 @@ } } }, + "model.RegionDetails": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "vNets": { + "type": "array", + "items": { + "$ref": "#/definitions/model.VNetDetails" + } + } + } + }, "model.RegionInfo": { "type": "object", "properties": { @@ -14424,6 +14424,20 @@ } } }, + "model.VNetDetails": { + "type": "object", + "properties": { + "hostsPerSubnet": { + "type": "string" + }, + "subnetCount": { + "type": "string" + }, + "useFirstNZones": { + "type": "string" + } + } + }, "model.inspectOverview": { "type": "object", "properties": { @@ -14540,17 +14554,17 @@ "netutil.RestPostUtilToDesignVNetRequest": { "type": "object", "properties": { - "cspRegions": { + "desiredPrivateNetwork": { + "type": "string" + }, + "mcNetConfigurations": { "type": "array", "items": { - "$ref": "#/definitions/model.CspRegion" + "$ref": "#/definitions/model.McNetConfigurationDetails" } }, "supernettingEnabled": { "type": "string" - }, - "targetPrivateNetwork": { - "type": "string" } } }, diff --git a/src/api/rest/docs/swagger.yaml b/src/api/rest/docs/swagger.yaml index bf45d57b6..3a1637781 100644 --- a/src/api/rest/docs/swagger.yaml +++ b/src/api/rest/docs/swagger.yaml @@ -7866,15 +7866,6 @@ components: description: "CredentialReq contains the necessary information to register a\ \ credential. This includes the AES key encrypted with the RSA public key,\ \ which is then used to decrypt the AES key on the server side." - model.CspRegion: - type: object - properties: - connectionName: - type: string - neededVNets: - type: array - items: - $ref: '#/components/schemas/model.NeededVNet' model.CustomImageStatus: type: string enum: @@ -8193,6 +8184,15 @@ components: type: number longitude: type: number + model.McNetConfigurationDetails: + type: object + properties: + csp: + type: string + regions: + type: array + items: + $ref: '#/components/schemas/model.RegionDetails' model.McNlbInfo: type: object properties: @@ -8381,15 +8381,6 @@ components: type: string description: TCP|UDP example: TCP - model.NeededVNet: - type: object - properties: - subnetCount: - type: integer - subnetSize: - type: integer - zoneSelectionMethod: - type: string model.NsInfo: type: object properties: @@ -8526,6 +8517,15 @@ components: type: array items: type: string + model.RegionDetails: + type: object + properties: + name: + type: string + vNets: + type: array + items: + $ref: '#/components/schemas/model.VNetDetails' model.RegionInfo: type: object properties: @@ -10754,6 +10754,15 @@ components: resource-group-id: type: string example: "" + model.VNetDetails: + type: object + properties: + hostsPerSubnet: + type: string + subnetCount: + type: string + useFirstNZones: + type: string model.inspectOverview: type: object properties: @@ -10831,14 +10840,14 @@ components: netutil.RestPostUtilToDesignVNetRequest: type: object properties: - cspRegions: + desiredPrivateNetwork: + type: string + mcNetConfigurations: type: array items: - $ref: '#/components/schemas/model.CspRegion' + $ref: '#/components/schemas/model.McNetConfigurationDetails' supernettingEnabled: type: string - targetPrivateNetwork: - type: string netutil.RestPostUtilToValidateNetworkRequest: type: object properties: diff --git a/src/core/model/vnet.go b/src/core/model/vnet.go index 1f331b869..60b395673 100644 --- a/src/core/model/vnet.go +++ b/src/core/model/vnet.go @@ -77,20 +77,25 @@ type BastionNode struct { // VNetDesignRequest is a struct to handle the utility function, DesignVNet() type VNetDesignRequest struct { - TargetPrivateNetwork string `json:"targetPrivateNetwork"` - SupernettingEnabled string `json:"supernettingEnabled"` - CspRegions []CspRegion `json:"cspRegions"` + DesiredPrivateNetwork string `json:"desiredPrivateNetwork"` + SupernettingEnabled string `json:"supernettingEnabled"` + McNetConfigurations []McNetConfigurationDetails `json:"mcNetConfigurations"` } -type CspRegion struct { - ConnectionName string `json:"connectionName"` - NeededVNets []NeededVNet `json:"neededVNets"` +type McNetConfigurationDetails struct { + Csp string `json:"csp"` + Regions []RegionDetails `json:"regions"` } -type NeededVNet struct { - SubnetCount int `json:"subnetCount"` - SubnetSize int `json:"subnetSize"` - ZoneSelectionMethod string `json:"zoneSelectionMethod"` +type RegionDetails struct { + Name string `json:"name"` + VNets []VNetDetails `json:"vNets"` +} + +type VNetDetails struct { + SubnetCount string `json:"subnetCount"` + HostsPerSubnet string `json:"hostsPerSubnet"` + UseFirstNZones string `json:"useFirstNZones"` } type VNetDesignResponse struct { diff --git a/src/core/resource/subnet.go b/src/core/resource/subnet.go index 6772f419c..2ad803993 100644 --- a/src/core/resource/subnet.go +++ b/src/core/resource/subnet.go @@ -1410,14 +1410,34 @@ func DeregisterSubnet(nsId string, vNetId string, subnetId string) (model.Simple // GetFirstNZones returns the first N zones of the given connection func GetFirstNZones(connectionName string, firstN int) ([]string, int, error) { + // TODO: Update the validation logic + // It's a temporary validation logic due to the connection name pattern + // Splite the connectionName into provider and region parts := strings.SplitN(connectionName, "-", 2) provider := parts[0] - region := parts[1] + regionZone := parts[1] // Get the region details - regionDetail, err := common.GetRegion(provider, region) + regionsObj, err := common.GetRegions(provider) if err != nil { + log.Error().Err(err).Msg("") + return nil, 0, err + } + + // Try to match and get the region detail + var regionDetail model.RegionDetail + for _, region := range regionsObj.Regions { + exists := strings.HasPrefix(regionZone, region.RegionName) + if exists { + regionDetail = region + break + } + } + // Check if the region detail exists or not + if regionDetail.RegionName == "" && len(regionDetail.Zones) == 0 { + err := fmt.Errorf("invalid region/zone: %s", regionZone) + log.Error().Err(err).Msg("") return nil, 0, err } @@ -1429,7 +1449,7 @@ func GetFirstNZones(connectionName string, firstN int) ([]string, int, error) { length = firstN } - if zones == nil || len(zones) == 0 { + if len(zones) == 0 { return nil, 0, nil } diff --git a/src/core/resource/vnet.go b/src/core/resource/vnet.go index e81b1cab0..af5b35743 100644 --- a/src/core/resource/vnet.go +++ b/src/core/resource/vnet.go @@ -1557,7 +1557,7 @@ func DesignVNets(reqt *model.VNetDesignRequest) (model.VNetDesignResponse, error var vNetReqList []model.TbVNetReq var allCIDRs []string - baseIP, _, err := net.ParseCIDR(reqt.TargetPrivateNetwork) + baseIP, _, err := net.ParseCIDR(reqt.DesiredPrivateNetwork) if err != nil { log.Error().Err(err).Msg("") return model.VNetDesignResponse{}, err @@ -1566,62 +1566,91 @@ func DesignVNets(reqt *model.VNetDesignRequest) (model.VNetDesignResponse, error nextAvailableIP := baseIP idx := 0 - for i, region := range reqt.CspRegions { - for j, vnet := range region.NeededVNets { + for _, mcNetConf := range reqt.McNetConfigurations { + for _, region := range mcNetConf.Regions { + for k, vnet := range region.VNets { + + csp := mcNetConf.Csp + region := region.Name + connectionName := csp + "-" + region + connectionName = strings.ToLower(connectionName) + log.Debug().Msgf("CSP: %s, Region: %s", csp, region) + log.Debug().Msgf("connectionName: %s", connectionName) + + // Convert string to integer and check if it's valid + subnetCount, err := strconv.Atoi(vnet.SubnetCount) + if err != nil { + log.Error().Err(err).Msg("Failed to convert SubnetCount to integer") + return model.VNetDesignResponse{}, err + } - // Design a vNet - log.Debug().Msgf("Region %d, VNet %d:\n", i+1, j+1) + hostsPerSubent, err := strconv.Atoi(vnet.HostsPerSubnet) + if err != nil { + log.Error().Err(err).Msg("Failed to convert HostsPerSubnet to integer") + return model.VNetDesignResponse{}, err + } - // Calculate CIDR blocks for vNet and subnets - cidr, subnets, newNextAvailableIP, err := netutil.DeriveVNetAndSubnets(nextAvailableIP, vnet.SubnetSize, vnet.SubnetCount) - if err != nil { - log.Warn().Msgf("Error calculating subnets: %v", err) - continue - } - log.Debug().Msgf("vNet: %s", cidr) - vNetReq := model.TbVNetReq{ - Name: fmt.Sprintf("vnet%02d", idx), - ConnectionName: region.ConnectionName, - CidrBlock: cidr, - Description: fmt.Sprintf("vnet%02d designed by util/vNet/design", idx), - } + useFirstNZones, err := strconv.Atoi(vnet.UseFirstNZones) + if err != nil { + log.Error().Err(err).Msg("Failed to convert UseFirstNZones to integer") + return model.VNetDesignResponse{}, err + } - log.Debug().Msgf("Subnets:") - zones, length, err := GetFirstNZones(region.ConnectionName, 2) - if err != nil { - log.Error().Err(err).Msg("") - } + // Design a vNet + log.Debug().Msgf("CSP: %s, Region %s, VNet %02d:\n", mcNetConf.Csp, region, k+1) - for k, subnet := range subnets { - subnetReq := model.TbSubnetReq{} - subnetReq.IPv4_CIDR = subnet - - // Note - Depending on the input, a few more subnets can be created - if k < vnet.SubnetCount { - subnetReq.Name = fmt.Sprintf("subnet%02d", k) - subnetReq.Description = fmt.Sprintf("subnet%02d designed by util/vNet/design", k) - } else { - subnetReq.Name = fmt.Sprintf("subnet%02d-reserved", k) - subnetReq.Description = fmt.Sprintf("subnet%02d-reserved designed by util/vNet/design", k) + // Calculate CIDR blocks for vNet and subnets + cidr, subnets, newNextAvailableIP, err := netutil.DeriveVNetAndSubnets(nextAvailableIP, hostsPerSubent, subnetCount) + if err != nil { + log.Warn().Msgf("Error calculating subnets: %v", err) + continue + } + log.Debug().Msgf("vNet: %s", cidr) + vNetReq := model.TbVNetReq{ + Name: fmt.Sprintf("vnet%02d", idx), + ConnectionName: connectionName, + CidrBlock: cidr, + Description: fmt.Sprintf("vnet%02d designed by util/vNet/design", idx), } - // Zone selection method: firstTwoZones - if length > 0 { - subnetReq.Zone = zones[k%length] - } else { - subnetReq.Zone = "" + log.Debug().Msgf("Subnets:") + zones, length, err := GetFirstNZones(connectionName, useFirstNZones) + if err != nil { + log.Error().Err(err).Msg("") } - // Add the subnet to the vNet - vNetReq.SubnetInfoList = append(vNetReq.SubnetInfoList, subnetReq) - } - nextAvailableIP = newNextAvailableIP + for l, subnet := range subnets { + subnetReq := model.TbSubnetReq{} + subnetReq.IPv4_CIDR = subnet + + // Note - Depending on the input, a few more subnets can be created + if l < subnetCount { + subnetReq.Name = fmt.Sprintf("subnet%02d", l) + subnetReq.Description = fmt.Sprintf("subnet%02d designed by util/vNet/design", l) + } else { + subnetReq.Name = fmt.Sprintf("subnet%02d-reserved", l) + subnetReq.Description = fmt.Sprintf("subnet%02d-reserved designed by util/vNet/design", l) + } - // Keep all CIDRs for supernetting - allCIDRs = append(allCIDRs, cidr) + // Zone selection method: firstNZones + if length > 0 { + subnetReq.Zone = zones[l%length] + } else { + subnetReq.Zone = "" + } - // Add the vNet to the list - vNetReqList = append(vNetReqList, vNetReq) + // Add the subnet to the vNet + vNetReq.SubnetInfoList = append(vNetReq.SubnetInfoList, subnetReq) + } + nextAvailableIP = newNextAvailableIP + + // Keep all CIDRs for supernetting + allCIDRs = append(allCIDRs, cidr) + + // Add the vNet to the list + vNetReqList = append(vNetReqList, vNetReq) + idx++ + } } } vNetDesignResp.VNetReqList = vNetReqList