sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/scope/shared.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scope
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/go-logr/logr"
    23  	"github.com/pkg/errors"
    24  
    25  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    26  )
    27  
    28  var (
    29  	// ErrAZSubnetsNotFound is an error when a availability zone is specified but there are
    30  	// no matching subnets for that availability zone (a.k.a. fault domain).
    31  	ErrAZSubnetsNotFound = errors.New("no subnets found for supplied availability zone")
    32  	// ErrLoggerRequired is an error if a logger isn't specified.
    33  	ErrLoggerRequired = errors.New("logger is required")
    34  	// ErrNotPlaced is an error if there is no placement determined.
    35  	ErrNotPlaced = errors.New("placement not determined")
    36  )
    37  
    38  type placementInput struct {
    39  	SpecSubnetIDs           []string
    40  	SpecAvailabilityZones   []string
    41  	ParentAvailabilityZones []string
    42  	ControlplaneSubnets     infrav1.Subnets
    43  }
    44  
    45  type subnetsPlacementStratgey interface {
    46  	Place(input *placementInput) ([]string, error)
    47  }
    48  
    49  func newDefaultSubnetPlacementStrategy(logger *logr.Logger) (subnetsPlacementStratgey, error) {
    50  	if logger == nil {
    51  		return nil, ErrLoggerRequired
    52  	}
    53  
    54  	return &defaultSubnetPlacementStrategy{
    55  		logger: *logger,
    56  	}, nil
    57  }
    58  
    59  // defaultSubnetPlacementStrategy is the default strategy for subnet placement.
    60  type defaultSubnetPlacementStrategy struct {
    61  	logger logr.Logger
    62  }
    63  
    64  // Place works out the subnet placement based on the following precedence:
    65  // 1. Explicit definition of subnet IDs in the spec
    66  // 2. If the spec has Availability Zones then get the subnets for these AZs
    67  // 3. If the parent resource has Availability Zones then get the subnets for these AZs
    68  // 4. All the private subnets from the control plane are used
    69  // In Cluster API Availability Zone can also be referred to by the name `Failure Domain`.
    70  func (p *defaultSubnetPlacementStrategy) Place(input *placementInput) ([]string, error) {
    71  	if len(input.SpecSubnetIDs) > 0 {
    72  		p.logger.V(2).Info("using subnets from the spec")
    73  		return input.SpecSubnetIDs, nil
    74  	}
    75  
    76  	if len(input.SpecAvailabilityZones) > 0 {
    77  		p.logger.V(2).Info("determining subnets to use from the spec availability zones")
    78  		subnetIDs, err := p.getSubnetsForAZs(input.SpecAvailabilityZones, input.ControlplaneSubnets)
    79  		if err != nil {
    80  			return nil, fmt.Errorf("getting subnets for spec azs: %w", err)
    81  		}
    82  
    83  		return subnetIDs, nil
    84  	}
    85  
    86  	if len(input.ParentAvailabilityZones) > 0 {
    87  		p.logger.V(2).Info("determining subnets to use from the parents availability zones")
    88  		subnetIDs, err := p.getSubnetsForAZs(input.ParentAvailabilityZones, input.ControlplaneSubnets)
    89  		if err != nil {
    90  			return nil, fmt.Errorf("getting subnets for parent azs: %w", err)
    91  		}
    92  
    93  		return subnetIDs, nil
    94  	}
    95  
    96  	controlPlaneSubnetIDs := input.ControlplaneSubnets.FilterPrivate().IDs()
    97  	if len(controlPlaneSubnetIDs) > 0 {
    98  		p.logger.V(2).Info("using all the private subnets from the control plane")
    99  		return controlPlaneSubnetIDs, nil
   100  	}
   101  
   102  	return nil, ErrNotPlaced
   103  }
   104  
   105  func (p *defaultSubnetPlacementStrategy) getSubnetsForAZs(azs []string, controlPlaneSubnets infrav1.Subnets) ([]string, error) {
   106  	subnetIDs := []string{}
   107  
   108  	for _, zone := range azs {
   109  		subnets := controlPlaneSubnets.FilterByZone(zone)
   110  		if len(subnets) == 0 {
   111  			return nil, fmt.Errorf("getting subnets for availability zone %s: %w", zone, ErrAZSubnetsNotFound)
   112  		}
   113  		subnetIDs = append(subnetIDs, subnets.IDs()...)
   114  	}
   115  
   116  	return subnetIDs, nil
   117  }