@@ -15,12 +15,16 @@ import (
1515 "github.com/openshift/installer/pkg/types"
1616)
1717
18+ // subnetsInput handles subnets information gathered from metadata.
1819type subnetsInput struct {
1920 vpc string
2021 privateSubnets aws.Subnets
2122 publicSubnets aws.Subnets
23+ edgeSubnets aws.Subnets
2224}
2325
26+ // zonesInput handles input parameters required to create managed and unmanaged
27+ // Subnets to CAPI.
2428type zonesInput struct {
2529 InstallConfig * installconfig.InstallConfig
2630 Cluster * capa.AWSCluster
@@ -49,56 +53,69 @@ func (zin *zonesInput) GatherSubnetsFromMetadata(ctx context.Context) (err error
4953 if zin .Subnets .publicSubnets , err = zin .InstallConfig .AWS .PublicSubnets (ctx ); err != nil {
5054 return fmt .Errorf ("failed to get public subnets: %w" , err )
5155 }
56+ if zin .Subnets .edgeSubnets , err = zin .InstallConfig .AWS .EdgeSubnets (ctx ); err != nil {
57+ return fmt .Errorf ("failed to get edge subnets: %w" , err )
58+ }
5259 if zin .Subnets .vpc , err = zin .InstallConfig .AWS .VPC (ctx ); err != nil {
5360 return fmt .Errorf ("failed to get VPC: %w" , err )
5461 }
5562 return nil
5663}
5764
58- type zonesCAPI struct {
59- controlPlaneZones sets.Set [string ]
60- computeZones sets.Set [string ]
65+ // ZonesCAPI handles the discovered zones used to create subnets to CAPA.
66+ // ZonesCAPI is scoped in this package, but exported to use complex scenarios
67+ // with go-cmp on unit tests.
68+ type ZonesCAPI struct {
69+ ControlPlaneZones sets.Set [string ]
70+ ComputeZones sets.Set [string ]
71+ EdgeZones sets.Set [string ]
72+ }
73+
74+ // GetAvailabilityZones returns a sorted union of Availability Zones defined
75+ // in the zone attribute in the pools for control plane and compute.
76+ func (zo * ZonesCAPI ) GetAvailabilityZones () []string {
77+ return sets .List (zo .ControlPlaneZones .Union (zo .ComputeZones ))
6178}
6279
63- // AvailabilityZones returns a sorted union of Availability Zones defined
64- // in the zone attribute in the pools for control plane and compute zones .
65- func (zo * zonesCAPI ) AvailabilityZones () []string {
66- return sets .List (zo .controlPlaneZones . Union ( zo . computeZones ) )
80+ // GetEdgeZones returns a sorted union of Local Zones or Wavelength Zones
81+ // defined in the zone attribute in the edge compute pool .
82+ func (zo * ZonesCAPI ) GetEdgeZones () []string {
83+ return sets .List (zo .EdgeZones )
6784}
6885
6986// SetAvailabilityZones insert the zone to the given compute pool, and to
7087// the regular zone (zone type availability-zone) list.
71- func (zo * zonesCAPI ) SetAvailabilityZones (pool string , zones []string ) {
88+ func (zo * ZonesCAPI ) SetAvailabilityZones (pool string , zones []string ) {
7289 switch pool {
7390 case types .MachinePoolControlPlaneRoleName :
74- zo .controlPlaneZones .Insert (zones ... )
91+ zo .ControlPlaneZones .Insert (zones ... )
7592
7693 case types .MachinePoolComputeRoleName :
77- zo .computeZones .Insert (zones ... )
94+ zo .ComputeZones .Insert (zones ... )
7895 }
7996}
8097
8198// SetDefaultConfigZones evaluates if machine pools (control plane and workers) have been
8299// set the zones from install-config.yaml, if not sets the default from platform, when exists,
83100// otherwise set the default from the region discovered from AWS API.
84- func (zo * zonesCAPI ) SetDefaultConfigZones (pool string , defConfig []string , defRegion []string ) {
101+ func (zo * ZonesCAPI ) SetDefaultConfigZones (pool string , defConfig []string , defRegion []string ) {
85102 zones := []string {}
86103 switch pool {
87104 case types .MachinePoolControlPlaneRoleName :
88- if len (zo .controlPlaneZones ) == 0 && len (defConfig ) > 0 {
105+ if len (zo .ControlPlaneZones ) == 0 && len (defConfig ) > 0 {
89106 zones = defConfig
90- } else if len (zo .controlPlaneZones ) == 0 {
107+ } else if len (zo .ControlPlaneZones ) == 0 {
91108 zones = defRegion
92109 }
93- zo .controlPlaneZones .Insert (zones ... )
110+ zo .ControlPlaneZones .Insert (zones ... )
94111
95112 case types .MachinePoolComputeRoleName :
96- if len (zo .computeZones ) == 0 && len (defConfig ) > 0 {
113+ if len (zo .ComputeZones ) == 0 && len (defConfig ) > 0 {
97114 zones = defConfig
98- } else if len (zo .computeZones ) == 0 {
115+ } else if len (zo .ComputeZones ) == 0 {
99116 zones = defRegion
100117 }
101- zo .computeZones .Insert (zones ... )
118+ zo .ComputeZones .Insert (zones ... )
102119 }
103120}
104121
@@ -118,13 +135,16 @@ func setSubnets(ctx context.Context, in *zonesInput) error {
118135 if in .Cluster == nil {
119136 return fmt .Errorf ("failed to get AWSCluster config" )
120137 }
138+
139+ // BYO VPC ("unmanaged") deployments
121140 if len (in .InstallConfig .Config .AWS .Subnets ) > 0 {
122141 if err := in .GatherSubnetsFromMetadata (ctx ); err != nil {
123142 return fmt .Errorf ("failed to get subnets from metadata: %w" , err )
124143 }
125144 return setSubnetsBYOVPC (in )
126145 }
127146
147+ // Managed VPC (fully automated) deployments
128148 if err := in .GatherZonesFromMetadata (ctx ); err != nil {
129149 return fmt .Errorf ("failed to get availability zones from metadata: %w" , err )
130150 }
@@ -133,10 +153,10 @@ func setSubnets(ctx context.Context, in *zonesInput) error {
133153
134154// setSubnetsBYOVPC creates the CAPI NetworkSpec.Subnets setting the
135155// desired subnets from install-config.yaml in the BYO VPC deployment.
136- // This function does not have support for unit test to mock for AWS API,
156+ // This function does not provide support for unit test to mock for AWS API,
137157// so all API calls must be done prior this execution.
138- // TODO: create support to mock AWS API calls in the unit tests, so we can merge
139- // the methods GatherSubnetsFromMetadata() into this .
158+ // TODO: create support to mock AWS API calls in the unit tests, then the method
159+ // GatherSubnetsFromMetadata() can be added in setSubnetsBYOVPC .
140160func setSubnetsBYOVPC (in * zonesInput ) error {
141161 in .Cluster .Spec .NetworkSpec .VPC = capa.VPCSpec {
142162 ID : in .Subnets .vpc ,
@@ -159,64 +179,90 @@ func setSubnetsBYOVPC(in *zonesInput) error {
159179 })
160180 }
161181
182+ // edgeSubnets are subnet created on AWS Local Zones or Wavelength Zone,
183+ // discovered by ID and zone-type attribute.
184+ for _ , subnet := range in .Subnets .edgeSubnets {
185+ in .Cluster .Spec .NetworkSpec .Subnets = append (in .Cluster .Spec .NetworkSpec .Subnets , capa.SubnetSpec {
186+ ID : subnet .ID ,
187+ CidrBlock : subnet .CIDR ,
188+ AvailabilityZone : subnet .Zone .Name ,
189+ IsPublic : subnet .Public ,
190+ })
191+ }
192+
162193 return nil
163194}
164195
165196// setSubnetsManagedVPC creates the CAPI NetworkSpec.VPC and the NetworkSpec.Subnets,
166197// setting the desired zones from install-config.yaml in the managed
167198// VPC deployment, when specified, otherwise default zones are set from
168- // the previously discovered from AWS API.
169- // This function does not have mock for AWS API, so all API calls must be done prior
170- // this execution.
171- // TODO: create support to mock AWS API calls in the unit tests, so we can merge
172- // the methods GatherZonesFromMetadata() into this.
199+ // the AWS API, previously discovered.
173200// The CIDR blocks are calculated leaving free blocks to allow future expansions,
174201// in Day-2, when desired.
202+ // This function does not have mock for AWS API, so all API calls must be added prior
203+ // this execution.
204+ // TODO: create support to mock AWS API calls in the unit tests, then the method
205+ // GatherZonesFromMetadata() can be added in setSubnetsManagedVPC.
175206func setSubnetsManagedVPC (in * zonesInput ) error {
176207 out , err := extractZonesFromInstallConfig (in )
177208 if err != nil {
178209 return fmt .Errorf ("failed to get availability zones: %w" , err )
179210 }
180211
181- allZones := out .AvailabilityZones ()
182212 isPublishingExternal := in .InstallConfig .Config .Publish == types .ExternalPublishingStrategy
213+ allAvailabilityZones := out .GetAvailabilityZones ()
214+ allEdgeZones := out .GetEdgeZones ()
215+
183216 mainCIDR := capiutils .CIDRFromInstallConfig (in .InstallConfig )
184217 in .Cluster .Spec .NetworkSpec .VPC = capa.VPCSpec {
185218 CidrBlock : mainCIDR .String (),
186219 }
187220
188- // Base subnets considering only private zones, leaving one free block to allow
221+ // Base subnets count considering only private zones, leaving one free block to allow
189222 // future subnet expansions in Day-2.
190- numSubnets := len (allZones ) + 1
223+ numSubnets := len (allAvailabilityZones ) + 1
191224
192- // Public subnets consumes one range from base blocks .
225+ // Public subnets consumes one range from private CIDR block .
193226 if isPublishingExternal {
194227 numSubnets ++
195228 }
196229
230+ // Edge subnets consumes one CIDR block from private CIDR, slicing it
231+ // into smaller depending on the amount edge zones added to install config.
232+ if len (allEdgeZones ) > 0 {
233+ numSubnets ++
234+ }
235+
197236 privateCIDRs , err := utilscidr .SplitIntoSubnetsIPv4 (mainCIDR .String (), numSubnets )
198237 if err != nil {
199- return fmt .Errorf ("unable to retrieve CIDR blocks for all private subnets: %w" , err )
238+ return fmt .Errorf ("unable to generate CIDR blocks for all private subnets: %w" , err )
239+ }
240+
241+ publicCIDR := privateCIDRs [len (allAvailabilityZones )].String ()
242+
243+ var edgeCIDR string
244+ if len (allEdgeZones ) > 0 {
245+ edgeCIDR = privateCIDRs [len (allAvailabilityZones )+ 1 ].String ()
200246 }
201247
202248 var publicCIDRs []* net.IPNet
203249 if isPublishingExternal {
204250 // The last num(zones) blocks are dedicated to the public subnets.
205- publicCIDRs , err = utilscidr .SplitIntoSubnetsIPv4 (privateCIDRs [ len ( allZones )]. String () , len (allZones ))
251+ publicCIDRs , err = utilscidr .SplitIntoSubnetsIPv4 (publicCIDR , len (allAvailabilityZones ))
206252 if err != nil {
207- return fmt .Errorf ("unable to retrieve CIDR blocks for all public subnets: %w" , err )
253+ return fmt .Errorf ("unable to generate CIDR blocks for all public subnets: %w" , err )
208254 }
209255 }
210256
211- // Create subnets from zone pool with type availability-zone
212- if len (privateCIDRs ) < len (allZones ) {
257+ // Create subnets from zone pools (control plane and compute) with type availability-zone.
258+ if len (privateCIDRs ) < len (allAvailabilityZones ) {
213259 return fmt .Errorf ("unable to define CIDR blocks to all zones for private subnets" )
214260 }
215- if isPublishingExternal && len (publicCIDRs ) < len (allZones ) {
261+ if isPublishingExternal && len (publicCIDRs ) < len (allAvailabilityZones ) {
216262 return fmt .Errorf ("unable to define CIDR blocks to all zones for public subnets" )
217263 }
218264
219- for idxCIDR , zone := range allZones {
265+ for idxCIDR , zone := range allAvailabilityZones {
220266 in .Cluster .Spec .NetworkSpec .Subnets = append (in .Cluster .Spec .NetworkSpec .Subnets , capa.SubnetSpec {
221267 AvailabilityZone : zone ,
222268 CidrBlock : privateCIDRs [idxCIDR ].String (),
@@ -232,14 +278,63 @@ func setSubnetsManagedVPC(in *zonesInput) error {
232278 })
233279 }
234280 }
281+
282+ // no edge zones, nothing else to do
283+ if len (allEdgeZones ) == 0 {
284+ return nil
285+ }
286+
287+ // Create subnets from edge zone pool with type local-zone.
288+
289+ // Slice the main CIDR (edgeCIDR) into N*zones for privates subnets,
290+ // and, when publish external, duplicate to create public subnets.
291+ numEdgeSubnets := len (allEdgeZones )
292+ if isPublishingExternal {
293+ numEdgeSubnets *= 2
294+ }
295+
296+ // Allow one CIDR block for future expansion.
297+ numEdgeSubnets ++
298+
299+ // Slice the edgeCIDR into the amount of desired subnets.
300+ edgeCIDRs , err := utilscidr .SplitIntoSubnetsIPv4 (edgeCIDR , numEdgeSubnets )
301+ if err != nil {
302+ return fmt .Errorf ("unable to generate CIDR blocks for all edge subnets: %w" , err )
303+ }
304+ if len (edgeCIDRs ) < len (allEdgeZones ) {
305+ return fmt .Errorf ("unable to define CIDR blocks to all edge zones for private subnets" )
306+ }
307+ if isPublishingExternal && (len (edgeCIDRs ) < (len (allEdgeZones ) * 2 )) {
308+ return fmt .Errorf ("unable to define CIDR blocks to all edge zones for public subnets" )
309+ }
310+
311+ // Create subnets from zone pool with type local-zone or wavelength-zone (edge zones)
312+ for idxCIDR , zone := range allEdgeZones {
313+ in .Cluster .Spec .NetworkSpec .Subnets = append (in .Cluster .Spec .NetworkSpec .Subnets , capa.SubnetSpec {
314+ AvailabilityZone : zone ,
315+ CidrBlock : edgeCIDRs [idxCIDR ].String (),
316+ ID : fmt .Sprintf ("%s-subnet-private-%s" , in .ClusterID .InfraID , zone ),
317+ IsPublic : false ,
318+ })
319+ if isPublishingExternal {
320+ in .Cluster .Spec .NetworkSpec .Subnets = append (in .Cluster .Spec .NetworkSpec .Subnets , capa.SubnetSpec {
321+ AvailabilityZone : zone ,
322+ CidrBlock : edgeCIDRs [len (allEdgeZones )+ idxCIDR ].String (),
323+ ID : fmt .Sprintf ("%s-subnet-public-%s" , in .ClusterID .InfraID , zone ),
324+ IsPublic : true ,
325+ })
326+ }
327+ }
328+
235329 return nil
236330}
237331
238332// extractZonesFromInstallConfig extracts zones defined in the install-config.
239- func extractZonesFromInstallConfig (in * zonesInput ) (* zonesCAPI , error ) {
240- out := zonesCAPI {
241- controlPlaneZones : sets .New [string ](),
242- computeZones : sets .New [string ](),
333+ func extractZonesFromInstallConfig (in * zonesInput ) (* ZonesCAPI , error ) {
334+ out := ZonesCAPI {
335+ ControlPlaneZones : sets .New [string ](),
336+ ComputeZones : sets .New [string ](),
337+ EdgeZones : sets .New [string ](),
243338 }
244339
245340 cfg := in .InstallConfig .Config
@@ -253,19 +348,35 @@ func extractZonesFromInstallConfig(in *zonesInput) (*zonesCAPI, error) {
253348 }
254349 out .SetDefaultConfigZones (types .MachinePoolControlPlaneRoleName , defaultZones , in .ZonesInRegion )
255350
351+ // set the zones in the compute/worker pool, when defined, otherwise use defaults.
256352 for _ , pool := range cfg .Compute {
257353 if pool .Platform .AWS == nil {
258354 continue
259355 }
260- if len (pool .Platform .AWS .Zones ) > 0 {
261- out .SetAvailabilityZones (pool .Name , pool .Platform .AWS .Zones )
262- }
263- // Ignoring as edge pool is not yet supported by CAPA.
264- // See https://github.com/openshift/installer/pull/8173
356+ // edge compute pools should have zones defined.
265357 if pool .Name == types .MachinePoolEdgeRoleName {
358+ if len (pool .Platform .AWS .Zones ) == 0 {
359+ return nil , fmt .Errorf ("expect one or more zones in the edge compute pool, got: %q" , pool .Platform .AWS .Zones )
360+ }
361+ out .EdgeZones .Insert (pool .Platform .AWS .Zones ... )
266362 continue
267363 }
364+
365+ if len (pool .Platform .AWS .Zones ) > 0 {
366+ out .SetAvailabilityZones (pool .Name , pool .Platform .AWS .Zones )
367+ }
368+ out .SetDefaultConfigZones (types .MachinePoolComputeRoleName , defaultZones , in .ZonesInRegion )
369+ }
370+
371+ // set defaults for worker pool when not defined in config.
372+ if len (out .ComputeZones ) == 0 {
268373 out .SetDefaultConfigZones (types .MachinePoolComputeRoleName , defaultZones , in .ZonesInRegion )
269374 }
375+
376+ // should raise an error if no zones is available in the pools, default platform config, or metadata.
377+ if azs := out .GetAvailabilityZones (); len (azs ) == 0 {
378+ return nil , fmt .Errorf ("failed to set zones from config, got: %q" , azs )
379+ }
380+
270381 return & out , nil
271382}
0 commit comments