github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/provider/aws_provider.go (about) 1 package provider 2 3 import ( 4 "fmt" 5 "math/big" 6 "math/rand" 7 "net/netip" 8 "strings" 9 10 "github.com/kyma-project/kyma-environment-broker/internal/networking" 11 12 "github.com/kyma-project/kyma-environment-broker/internal/ptr" 13 14 "github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema" 15 "github.com/kyma-project/kyma-environment-broker/internal" 16 "github.com/kyma-project/kyma-environment-broker/internal/broker" 17 ) 18 19 const ( 20 DefaultAWSRegion = "eu-central-1" 21 DefaultAWSTrialRegion = "eu-west-1" 22 DefaultEuAccessAWSRegion = "eu-central-1" 23 DefaultAWSMultiZoneCount = 3 24 ) 25 26 var europeAWS = "eu-west-1" 27 var usAWS = "us-east-1" 28 var asiaAWS = "ap-southeast-1" 29 30 var toAWSSpecific = map[string]string{ 31 string(broker.Europe): europeAWS, 32 string(broker.Us): usAWS, 33 string(broker.Asia): asiaAWS, 34 } 35 36 type ( 37 AWSInput struct { 38 MultiZone bool 39 ControlPlaneFailureTolerance string 40 } 41 AWSTrialInput struct { 42 PlatformRegionMapping map[string]string 43 } 44 AWSFreemiumInput struct{} 45 ) 46 47 func (p *AWSInput) Defaults() *gqlschema.ClusterConfigInput { 48 zonesCount := 1 49 if p.MultiZone { 50 zonesCount = DefaultAWSMultiZoneCount 51 } 52 var controlPlaneFailureTolerance *string = nil 53 if p.ControlPlaneFailureTolerance != "" { 54 controlPlaneFailureTolerance = &p.ControlPlaneFailureTolerance 55 } 56 return &gqlschema.ClusterConfigInput{ 57 GardenerConfig: &gqlschema.GardenerConfigInput{ 58 DiskType: ptr.String("gp2"), 59 VolumeSizeGb: ptr.Integer(50), 60 MachineType: "m5.xlarge", 61 Region: DefaultAWSRegion, 62 Provider: "aws", 63 WorkerCidr: networking.DefaultNodesCIDR, 64 AutoScalerMin: 3, 65 AutoScalerMax: 20, 66 MaxSurge: zonesCount, 67 MaxUnavailable: 0, 68 ProviderSpecificConfig: &gqlschema.ProviderSpecificInput{ 69 AwsConfig: &gqlschema.AWSProviderConfigInput{ 70 VpcCidr: networking.DefaultNodesCIDR, 71 AwsZones: generateAWSZones(networking.DefaultNodesCIDR, MultipleZonesForAWSRegion(DefaultAWSRegion, zonesCount)), 72 }, 73 }, 74 ControlPlaneFailureTolerance: controlPlaneFailureTolerance, 75 }, 76 } 77 } 78 79 // awsZones defines a possible suffixes for given AWS regions 80 // The table is tested in a unit test to check if all necessary regions are covered 81 var awsZones = map[string]string{ 82 "eu-central-1": "abc", 83 "eu-west-2": "abc", 84 "ca-central-1": "abd", 85 "sa-east-1": "abc", 86 "us-east-1": "abcdf", 87 "us-west-1": "ab", 88 "ap-northeast-1": "acd", 89 "ap-northeast-2": "abc", 90 "ap-south-1": "abc", 91 "ap-southeast-1": "abc", 92 "ap-southeast-2": "abc", 93 } 94 95 func ZoneForAWSRegion(region string) string { 96 zones, found := awsZones[region] 97 if !found { 98 zones = "a" 99 } 100 101 zone := string(zones[rand.Intn(len(zones))]) 102 return fmt.Sprintf("%s%s", region, zone) 103 } 104 105 func MultipleZonesForAWSRegion(region string, zonesCount int) []string { 106 zones, found := awsZones[region] 107 if !found { 108 zones = "a" 109 zonesCount = 1 110 } 111 112 availableZones := strings.Split(zones, "") 113 rand.Shuffle(len(availableZones), func(i, j int) { availableZones[i], availableZones[j] = availableZones[j], availableZones[i] }) 114 if zonesCount > len(availableZones) { 115 // get maximum number of zones for region 116 zonesCount = len(availableZones) 117 } 118 119 availableZones = availableZones[:zonesCount] 120 121 var generatedZones []string 122 for _, zone := range availableZones { 123 generatedZones = append(generatedZones, fmt.Sprintf("%s%s", region, zone)) 124 } 125 return generatedZones 126 } 127 128 /* 129 * 130 generateAWSZones - creates a list of AWSZoneInput objects which contains a proper IP ranges. 131 It generates subnets - the subnets in AZ must be inside of the cidr block and non overlapping. example values: 132 cidr: 10.250.0.0/16 133 - name: eu-central-1a 134 workers: 10.250.0.0/19 135 public: 10.250.32.0/20 136 internal: 10.250.48.0/20 137 - name: eu-central-1b 138 workers: 10.250.64.0/19 139 public: 10.250.96.0/20 140 internal: 10.250.112.0/20 141 - name: eu-central-1c 142 workers: 10.250.128.0/19 143 public: 10.250.160.0/20 144 internal: 10.250.176.0/20 145 */ 146 func generateAWSZones(workerCidr string, zoneNames []string) []*gqlschema.AWSZoneInput { 147 var zones []*gqlschema.AWSZoneInput 148 149 cidr, _ := netip.ParsePrefix(workerCidr) 150 workerPrefixLength := cidr.Bits() + 3 151 workerPrefix, _ := cidr.Addr().Prefix(workerPrefixLength) 152 153 // delta - it is the difference between "public" and "internal" CIDRs, for example: 154 // WorkerCidr: "10.250.0.0/19", 155 // PublicCidr: "10.250.32.0/20", 156 // InternalCidr: "10.250.48.0/20", 157 // 4 * delta - difference between two worker (zone) CIDRs 158 delta := big.NewInt(1) 159 delta.Lsh(delta, uint(31-workerPrefixLength)) 160 161 // base - it is an integer, which is based on IP bytes 162 base := new(big.Int).SetBytes(workerPrefix.Addr().AsSlice()) 163 164 for _, name := range zoneNames { 165 zoneWorkerIP, _ := netip.AddrFromSlice(base.Bytes()) 166 zoneWorkerCidr := netip.PrefixFrom(zoneWorkerIP, workerPrefixLength) 167 168 base.Add(base, delta) 169 base.Add(base, delta) 170 publicIP, _ := netip.AddrFromSlice(base.Bytes()) 171 public := netip.PrefixFrom(publicIP, workerPrefixLength+1) 172 173 base.Add(base, delta) 174 internalIP, _ := netip.AddrFromSlice(base.Bytes()) 175 internalPrefix := netip.PrefixFrom(internalIP, workerPrefixLength+1) 176 177 zones = append(zones, &gqlschema.AWSZoneInput{ 178 Name: name, 179 WorkerCidr: zoneWorkerCidr.String(), 180 PublicCidr: public.String(), 181 InternalCidr: internalPrefix.String(), 182 }) 183 184 base.Add(base, delta) 185 } 186 187 return zones 188 } 189 190 func (p *AWSInput) ApplyParameters(input *gqlschema.ClusterConfigInput, pp internal.ProvisioningParameters) { 191 workerCidr := updateAWSWithWorkerCidr(input, pp) 192 zonesCount := 1 193 if p.MultiZone { 194 zonesCount = DefaultAWSMultiZoneCount 195 } 196 if len(pp.Parameters.Zones) > 0 { 197 zonesCount = len(pp.Parameters.Zones) 198 } 199 200 // if the region is provided, override the default one 201 if pp.Parameters.Region != nil && *pp.Parameters.Region != "" { 202 input.GardenerConfig.Region = *pp.Parameters.Region 203 } 204 205 // if the platformRegion is "EU Access" - switch the region to the eu-access 206 if internal.IsEuAccess(pp.PlatformRegion) { 207 input.GardenerConfig.Region = DefaultEuAccessAWSRegion 208 } 209 210 zones := pp.Parameters.Zones 211 // if zones are not provided (in the request) - generate it 212 if len(zones) == 0 { 213 zones = MultipleZonesForAWSRegion(input.GardenerConfig.Region, zonesCount) 214 } 215 // fill the input with proper zones having Worker CIDR 216 input.GardenerConfig.ProviderSpecificConfig.AwsConfig.AwsZones = generateAWSZones(workerCidr, zones) 217 } 218 219 func updateAWSWithWorkerCidr(input *gqlschema.ClusterConfigInput, pp internal.ProvisioningParameters) string { 220 workerCidr := networking.DefaultNodesCIDR 221 if pp.Parameters.Networking != nil { 222 workerCidr = pp.Parameters.Networking.NodesCidr 223 } 224 input.GardenerConfig.WorkerCidr = workerCidr 225 input.GardenerConfig.ProviderSpecificConfig.AwsConfig.VpcCidr = workerCidr 226 return workerCidr 227 } 228 229 func (p *AWSInput) Profile() gqlschema.KymaProfile { 230 return gqlschema.KymaProfileProduction 231 } 232 233 func (p *AWSInput) Provider() internal.CloudProvider { 234 return internal.AWS 235 } 236 237 func (p *AWSTrialInput) Defaults() *gqlschema.ClusterConfigInput { 238 return awsLiteDefaults(DefaultAWSTrialRegion) 239 } 240 241 func awsLiteDefaults(region string) *gqlschema.ClusterConfigInput { 242 return &gqlschema.ClusterConfigInput{ 243 GardenerConfig: &gqlschema.GardenerConfigInput{ 244 DiskType: ptr.String("gp2"), 245 VolumeSizeGb: ptr.Integer(50), 246 MachineType: "m5.xlarge", 247 Region: region, 248 Provider: "aws", 249 WorkerCidr: networking.DefaultNodesCIDR, 250 AutoScalerMin: 1, 251 AutoScalerMax: 1, 252 MaxSurge: 1, 253 MaxUnavailable: 0, 254 Purpose: &trialPurpose, 255 ProviderSpecificConfig: &gqlschema.ProviderSpecificInput{ 256 AwsConfig: &gqlschema.AWSProviderConfigInput{ 257 VpcCidr: networking.DefaultNodesCIDR, 258 AwsZones: generateAWSZones(networking.DefaultNodesCIDR, MultipleZonesForAWSRegion(region, 1)), 259 }, 260 }, 261 }, 262 } 263 } 264 265 func (p *AWSTrialInput) ApplyParameters(input *gqlschema.ClusterConfigInput, pp internal.ProvisioningParameters) { 266 params := pp.Parameters 267 268 if internal.IsEuAccess(pp.PlatformRegion) { 269 updateRegionWithZones(input, DefaultEuAccessAWSRegion) 270 return 271 } 272 273 // read platform region if exists 274 if pp.PlatformRegion != "" { 275 abstractRegion, found := p.PlatformRegionMapping[pp.PlatformRegion] 276 if found { 277 r := toAWSSpecific[abstractRegion] 278 updateRegionWithZones(input, r) 279 } 280 } 281 282 if params.Region != nil && *params.Region != "" { 283 r := toAWSSpecific[*params.Region] 284 updateRegionWithZones(input, r) 285 } 286 } 287 288 func updateRegionWithZones(input *gqlschema.ClusterConfigInput, region string) { 289 input.GardenerConfig.Region = region 290 input.GardenerConfig.ProviderSpecificConfig.AwsConfig.AwsZones[0].Name = ZoneForAWSRegion(region) 291 } 292 293 func (p *AWSTrialInput) Profile() gqlschema.KymaProfile { 294 return gqlschema.KymaProfileEvaluation 295 } 296 297 func (p *AWSTrialInput) Provider() internal.CloudProvider { 298 return internal.AWS 299 } 300 301 func (p *AWSFreemiumInput) Defaults() *gqlschema.ClusterConfigInput { 302 // Lite (freemium) must have the same defaults as Trial plan, but there was a requirement to change a region only for Trial. 303 defaults := awsLiteDefaults(DefaultAWSRegion) 304 305 return defaults 306 } 307 308 func (p *AWSFreemiumInput) ApplyParameters(input *gqlschema.ClusterConfigInput, pp internal.ProvisioningParameters) { 309 if pp.Parameters.Region != nil && *pp.Parameters.Region != "" && pp.Parameters.Zones == nil { 310 input.GardenerConfig.ProviderSpecificConfig.AwsConfig.AwsZones[0].Name = ZoneForAWSRegion(*pp.Parameters.Region) 311 } 312 } 313 314 func (p *AWSFreemiumInput) Profile() gqlschema.KymaProfile { 315 return gqlschema.KymaProfileEvaluation 316 } 317 318 func (p *AWSFreemiumInput) Provider() internal.CloudProvider { 319 return internal.AWS 320 }