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  }