@@ -20,6 +20,10 @@ import (
2020 "fmt"
2121 "sort"
2222 "time"
23+
24+ "github.com/aws/aws-sdk-go/aws"
25+ "github.com/aws/aws-sdk-go/service/ec2"
26+ "k8s.io/utils/ptr"
2327)
2428
2529const (
@@ -37,6 +41,11 @@ const (
3741 DefaultAPIServerHealthThresholdCount = 5
3842 // DefaultAPIServerUnhealthThresholdCount the API server unhealthy check threshold count.
3943 DefaultAPIServerUnhealthThresholdCount = 3
44+
45+ // ZoneTypeAvailabilityZone defines the regular AWS zones in the Region.
46+ ZoneTypeAvailabilityZone ZoneType = "availability-zone"
47+ // ZoneTypeLocalZone defines the AWS zone type in Local Zone infrastructure.
48+ ZoneTypeLocalZone ZoneType = "local-zone"
4049)
4150
4251// NetworkStatus encapsulates AWS networking resources.
@@ -508,6 +517,39 @@ type SubnetSpec struct {
508517
509518 // Tags is a collection of tags describing the resource.
510519 Tags Tags `json:"tags,omitempty"`
520+
521+ // ZoneType defines the type of the zone where the subnet is created.
522+ //
523+ // The valid values are availability-zone, and local-zone.
524+ //
525+ // Subnet with zone type availability-zone (regular) is always selected to create cluster
526+ // resources, like Load Balancers, NAT Gateways, Contol Plane nodes, etc.
527+ //
528+ // Subnet with zone type local-zone is not eligible to automatically create
529+ // regular cluster resources.
530+ //
531+ // The public subnet in availability-zone or local-zone is associated with regular public
532+ // route table with default route entry to a Internet Gateway.
533+ //
534+ // The private subnet in the availability-zone is associated with a private route table with
535+ // the default route entry to a NAT Gateway created in that zone.
536+ //
537+ // The private subnet in the local-zone is associated with a private route table with
538+ // the default route entry re-using the NAT Gateway in the Region (preferred from the
539+ // parent zone, the zone type availability-zone in the region, or first table available).
540+ //
541+ // +kubebuilder:validation:Enum=availability-zone;local-zone
542+ // +optional
543+ ZoneType * ZoneType `json:"zoneType,omitempty"`
544+
545+ // ParentZoneName is the zone name where the current subnet's zone is tied when
546+ // the zone is a Local Zone.
547+ //
548+ // The subnets in Local Zone locations consume the ParentZoneName to determine the correct
549+ // private route table to egress traffic to the internet.
550+ //
551+ // +optional
552+ ParentZoneName * string `json:"parentZoneName,omitempty"`
511553}
512554
513555// GetResourceID returns the identifier for this subnet,
@@ -524,6 +566,39 @@ func (s *SubnetSpec) String() string {
524566 return fmt .Sprintf ("id=%s/az=%s/public=%v" , s .GetResourceID (), s .AvailabilityZone , s .IsPublic )
525567}
526568
569+ // IsEdge returns the true when the subnet is created in the edge zone,
570+ // Local Zones.
571+ func (s * SubnetSpec ) IsEdge () bool {
572+ return s .ZoneType != nil && * s .ZoneType == ZoneTypeLocalZone
573+ }
574+
575+ // SetZoneInfo updates the subnets with zone information.
576+ func (s * SubnetSpec ) SetZoneInfo (zones []* ec2.AvailabilityZone ) error {
577+ zoneInfo := func (zoneName string ) * ec2.AvailabilityZone {
578+ for _ , zone := range zones {
579+ if aws .StringValue (zone .ZoneName ) == zoneName {
580+ return zone
581+ }
582+ }
583+ return nil
584+ }
585+
586+ zone := zoneInfo (s .AvailabilityZone )
587+ if zone == nil {
588+ if len (s .AvailabilityZone ) > 0 {
589+ return fmt .Errorf ("unable to update zone information for subnet '%v' and zone '%v'" , s .ID , s .AvailabilityZone )
590+ }
591+ return fmt .Errorf ("unable to update zone information for subnet '%v'" , s .ID )
592+ }
593+ if zone .ZoneType != nil {
594+ s .ZoneType = ptr .To (ZoneType (* zone .ZoneType ))
595+ }
596+ if zone .ParentZoneName != nil {
597+ s .ParentZoneName = zone .ParentZoneName
598+ }
599+ return nil
600+ }
601+
527602// Subnets is a slice of Subnet.
528603// +listType=map
529604// +listMapKey=id
@@ -541,6 +616,22 @@ func (s Subnets) ToMap() map[string]*SubnetSpec {
541616
542617// IDs returns a slice of the subnet ids.
543618func (s Subnets ) IDs () []string {
619+ res := []string {}
620+ for _ , subnet := range s {
621+ // Prevent returning edge zones (Local Zone) to regular Subnet IDs.
622+ // Edge zones should not deploy control plane nodes, and does not support Nat Gateway and
623+ // Network Load Balancers. Any resource for the core infrastructure should not consume edge
624+ // zones.
625+ if subnet .IsEdge () {
626+ continue
627+ }
628+ res = append (res , subnet .GetResourceID ())
629+ }
630+ return res
631+ }
632+
633+ // IDsWithEdge returns a slice of the subnet ids.
634+ func (s Subnets ) IDsWithEdge () []string {
544635 res := []string {}
545636 for _ , subnet := range s {
546637 res = append (res , subnet .GetResourceID ())
@@ -581,21 +672,29 @@ func (s Subnets) FindEqual(spec *SubnetSpec) *SubnetSpec {
581672// FilterPrivate returns a slice containing all subnets marked as private.
582673func (s Subnets ) FilterPrivate () (res Subnets ) {
583674 for _ , x := range s {
675+ // Subnets in AWS Local Zones or Wavelength should not be used by core infrastructure.
676+ if x .IsEdge () {
677+ continue
678+ }
584679 if ! x .IsPublic {
585680 res = append (res , x )
586681 }
587682 }
588- return
683+ return res
589684}
590685
591686// FilterPublic returns a slice containing all subnets marked as public.
592687func (s Subnets ) FilterPublic () (res Subnets ) {
593688 for _ , x := range s {
689+ // Subnets in AWS Local Zones or Wavelength should not be used by core infrastructure.
690+ if x .IsEdge () {
691+ continue
692+ }
594693 if x .IsPublic {
595694 res = append (res , x )
596695 }
597696 }
598- return
697+ return res
599698}
600699
601700// FilterByZone returns a slice containing all subnets that live in the availability zone specified.
@@ -605,22 +704,32 @@ func (s Subnets) FilterByZone(zone string) (res Subnets) {
605704 res = append (res , x )
606705 }
607706 }
608- return
707+ return res
609708}
610709
611710// GetUniqueZones returns a slice containing the unique zones of the subnets.
612711func (s Subnets ) GetUniqueZones () []string {
613712 keys := make (map [string ]bool )
614713 zones := []string {}
615714 for _ , x := range s {
616- if _ , value := keys [x .AvailabilityZone ]; ! value {
715+ if _ , value := keys [x .AvailabilityZone ]; len ( x . AvailabilityZone ) > 0 && ! value {
617716 keys [x .AvailabilityZone ] = true
618717 zones = append (zones , x .AvailabilityZone )
619718 }
620719 }
621720 return zones
622721}
623722
723+ // SetZoneInfo updates the subnets with zone information.
724+ func (s Subnets ) SetZoneInfo (zones []* ec2.AvailabilityZone ) error {
725+ for i := range s {
726+ if err := s [i ].SetZoneInfo (zones ); err != nil {
727+ return err
728+ }
729+ }
730+ return nil
731+ }
732+
624733// CNISpec defines configuration for CNI.
625734type CNISpec struct {
626735 // CNIIngressRules specify rules to apply to control plane and worker node security groups.
@@ -835,3 +944,11 @@ func (i *IngressRule) Equals(o *IngressRule) bool {
835944
836945 return true
837946}
947+
948+ // ZoneType defines listener AWS Availability Zone type.
949+ type ZoneType string
950+
951+ // String returns the string representation for the zone type.
952+ func (z ZoneType ) String () string {
953+ return string (z )
954+ }
0 commit comments