Skip to content

Commit 87b49a0

Browse files
committed
Add IPI installation on AWS dedicated hosts
1 parent ccad892 commit 87b49a0

File tree

11 files changed

+609
-49
lines changed

11 files changed

+609
-49
lines changed

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 216 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/aws/session"
9+
"github.com/aws/aws-sdk-go/service/ec2"
10+
"github.com/sirupsen/logrus"
11+
)
12+
13+
// Host holds metadata for a dedicated host.
14+
type Host struct {
15+
ID string
16+
Zone string
17+
}
18+
19+
// dedicatedHosts retrieves a list of dedicated hosts for the given region and
20+
// returns them in a map keyed by the host ID.
21+
func dedicatedHosts(ctx context.Context, session *session.Session, region string) (map[string]Host, error) {
22+
hostsByID := map[string]Host{}
23+
24+
client := ec2.New(session, aws.NewConfig().WithRegion(region))
25+
input := &ec2.DescribeHostsInput{}
26+
27+
if err := client.DescribeHostsPagesWithContext(ctx, input, func(page *ec2.DescribeHostsOutput, lastPage bool) bool {
28+
for _, h := range page.Hosts {
29+
id := aws.StringValue(h.HostId)
30+
if id == "" {
31+
// Skip entries lacking an ID (should not happen)
32+
continue
33+
}
34+
35+
logrus.Debugf("Found dedicatd host: %s", id)
36+
hostsByID[id] = Host{
37+
ID: id,
38+
Zone: aws.StringValue(h.AvailabilityZone),
39+
}
40+
}
41+
return !lastPage
42+
}); err != nil {
43+
return nil, fmt.Errorf("fetching dedicated hosts: %w", err)
44+
}
45+
46+
return hostsByID, nil
47+
}

pkg/asset/installconfig/aws/metadata.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type Metadata struct {
2727
vpc VPC
2828
instanceTypes map[string]InstanceType
2929

30+
Hosts map[string]Host
3031
Region string `json:"region,omitempty"`
3132
ProvidedSubnets []typesaws.Subnet `json:"subnets,omitempty"`
3233
Services []typesaws.ServiceEndpoint `json:"services,omitempty"`
@@ -390,3 +391,23 @@ func (m *Metadata) InstanceTypes(ctx context.Context) (map[string]InstanceType,
390391

391392
return m.instanceTypes, nil
392393
}
394+
395+
// DedicatedHosts retrieves all hosts available for use to verify against this installation for configured region.
396+
func (m *Metadata) DedicatedHosts(ctx context.Context) (map[string]Host, error) {
397+
m.mutex.Lock()
398+
defer m.mutex.Unlock()
399+
400+
if len(m.Hosts) == 0 {
401+
awsSession, err := m.unlockedSession(ctx)
402+
if err != nil {
403+
return nil, err
404+
}
405+
406+
m.Hosts, err = dedicatedHosts(ctx, awsSession, m.Region)
407+
if err != nil {
408+
return nil, fmt.Errorf("error listing dedicated hosts: %w", err)
409+
}
410+
}
411+
412+
return m.Hosts, nil
413+
}

pkg/asset/installconfig/aws/validation.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ func validateMachinePool(ctx context.Context, meta *Metadata, fldPath *field.Pat
466466
}
467467
}
468468

469+
allErrs = append(allErrs, validateHostPlacement(ctx, meta, fldPath, pool)...)
470+
469471
return allErrs
470472
}
471473

@@ -484,6 +486,36 @@ func translateEC2Arches(arches []string) sets.Set[string] {
484486
return res
485487
}
486488

489+
func validateHostPlacement(ctx context.Context, meta *Metadata, fldPath *field.Path, pool *awstypes.MachinePool) field.ErrorList {
490+
allErrs := field.ErrorList{}
491+
492+
if pool.HostPlacement == nil {
493+
return allErrs
494+
}
495+
496+
if pool.HostPlacement.Affinity != nil && *pool.HostPlacement.Affinity == awstypes.HostAffinityDedicatedHost {
497+
placementPath := fldPath.Child("hostPlacement")
498+
if pool.HostPlacement.DedicatedHost != nil {
499+
configuredHosts := pool.HostPlacement.DedicatedHost
500+
foundHosts, err := meta.DedicatedHosts(ctx)
501+
if err != nil {
502+
allErrs = append(allErrs, field.InternalError(placementPath.Child("dedicatedHost"), err))
503+
} else {
504+
// Check the returned configured hosts to see if the dedicated hosts defined in install-config exists.
505+
for _, host := range configuredHosts {
506+
_, ok := foundHosts[host.ID]
507+
if !ok {
508+
errMsg := fmt.Sprintf("dedicated host %s not found", host.ID)
509+
allErrs = append(allErrs, field.Invalid(placementPath.Child("dedicatedHost"), pool.InstanceType, errMsg))
510+
}
511+
}
512+
}
513+
}
514+
}
515+
516+
return allErrs
517+
}
518+
487519
func validateSecurityGroupIDs(ctx context.Context, meta *Metadata, fldPath *field.Path, platform *awstypes.Platform, pool *awstypes.MachinePool) field.ErrorList {
488520
allErrs := field.ErrorList{}
489521

pkg/asset/machines/aws/machines.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"k8s.io/apimachinery/pkg/runtime"
1212
"k8s.io/apimachinery/pkg/util/sets"
1313
"k8s.io/utils/pointer"
14+
"k8s.io/utils/ptr"
1415

1516
v1 "github.com/openshift/api/config/v1"
1617
machinev1 "github.com/openshift/api/machine/v1"
@@ -35,6 +36,7 @@ type machineProviderInput struct {
3536
userTags map[string]string
3637
publicSubnet bool
3738
securityGroupIDs []string
39+
dedicatedHost string
3840
}
3941

4042
// Machines returns a list of machines for a machinepool.
@@ -291,6 +293,15 @@ func provider(in *machineProviderInput) (*machineapi.AWSMachineProviderConfig, e
291293
config.MetadataServiceOptions.Authentication = machineapi.MetadataServiceAuthentication(in.imds.Authentication)
292294
}
293295

296+
if in.dedicatedHost != "" {
297+
config.HostPlacement = &machineapi.HostPlacement{
298+
Affinity: ptr.To(machineapi.HostAffinityDedicatedHost),
299+
DedicatedHost: &machineapi.DedicatedHost{
300+
ID: in.dedicatedHost,
301+
},
302+
}
303+
}
304+
294305
return config, nil
295306
}
296307

@@ -340,3 +351,18 @@ func ConfigMasters(machines []machineapi.Machine, controlPlane *machinev1.Contro
340351
providerSpec := controlPlane.Spec.Template.OpenShiftMachineV1Beta1Machine.Spec.ProviderSpec.Value.Object.(*machineapi.AWSMachineProviderConfig)
341352
providerSpec.LoadBalancers = lbrefs
342353
}
354+
355+
// DedicatedHost sets dedicated hosts for the specified zone.
356+
func DedicatedHost(hosts map[string]aws.Host, placement *awstypes.HostPlacement, zone string) string {
357+
// If install-config has HostPlacements configured, lets check the DedicatedHosts to see if one matches our region & zone.
358+
if placement != nil {
359+
// We only support one host ID currently for an instance. Need to also get host that matches the zone the machines will be put into.
360+
for _, host := range placement.DedicatedHost {
361+
hostDetails, found := hosts[host.ID]
362+
if found && hostDetails.Zone == zone {
363+
return hostDetails.ID
364+
}
365+
}
366+
}
367+
return ""
368+
}

pkg/asset/machines/aws/machinesets.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type MachineSetInput struct {
2525
Pool *types.MachinePool
2626
Role string
2727
UserDataSecret string
28+
Hosts map[string]icaws.Host
2829
}
2930

3031
// MachineSets returns a list of machinesets for a machinepool.
@@ -87,6 +88,8 @@ func MachineSets(in *MachineSetInput) ([]*machineapi.MachineSet, error) {
8788
instanceProfile = fmt.Sprintf("%s-worker-profile", in.ClusterID)
8889
}
8990

91+
dedicatedHost := DedicatedHost(in.Hosts, mpool.HostPlacement, az)
92+
9093
provider, err := provider(&machineProviderInput{
9194
clusterID: in.ClusterID,
9295
region: in.InstallConfigPlatformAWS.Region,
@@ -102,12 +105,21 @@ func MachineSets(in *MachineSetInput) ([]*machineapi.MachineSet, error) {
102105
userTags: in.InstallConfigPlatformAWS.UserTags,
103106
publicSubnet: publicSubnet,
104107
securityGroupIDs: in.Pool.Platform.AWS.AdditionalSecurityGroupIDs,
108+
dedicatedHost: dedicatedHost,
105109
})
106110
if err != nil {
107111
return nil, errors.Wrap(err, "failed to create provider")
108112
}
113+
114+
// If we are using any feature that is only available via CAPI, we must set the authoritativeAPI = ClusterAPI
115+
authoritativeAPI := machineapi.MachineAuthorityMachineAPI
116+
if isAuthoritativeClusterAPIRequired(provider) {
117+
authoritativeAPI = machineapi.MachineAuthorityClusterAPI
118+
}
119+
109120
name := fmt.Sprintf("%s-%s-%s", in.ClusterID, in.Pool.Name, az)
110121
spec := machineapi.MachineSpec{
122+
AuthoritativeAPI: authoritativeAPI,
111123
ProviderSpec: machineapi.ProviderSpec{
112124
Value: &runtime.RawExtension{Object: provider},
113125
},
@@ -130,7 +142,8 @@ func MachineSets(in *MachineSetInput) ([]*machineapi.MachineSet, error) {
130142
},
131143
},
132144
Spec: machineapi.MachineSetSpec{
133-
Replicas: &replicas,
145+
AuthoritativeAPI: authoritativeAPI,
146+
Replicas: &replicas,
134147
Selector: metav1.LabelSelector{
135148
MatchLabels: map[string]string{
136149
"machine.openshift.io/cluster-api-machineset": name,
@@ -151,8 +164,17 @@ func MachineSets(in *MachineSetInput) ([]*machineapi.MachineSet, error) {
151164
},
152165
},
153166
}
167+
154168
machinesets = append(machinesets, mset)
155169
}
156170

157171
return machinesets, nil
158172
}
173+
174+
// isAuthoritativeClusterAPIRequired is called to determine if the machine spec should have the AuthoritativeAPI set to ClusterAPI.
175+
func isAuthoritativeClusterAPIRequired(provider *machineapi.AWSMachineProviderConfig) bool {
176+
if provider.HostPlacement != nil && *provider.HostPlacement.Affinity != machineapi.HostAffinityAnyAvailable {
177+
return true
178+
}
179+
return false
180+
}

pkg/asset/machines/worker.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,16 @@ func (w *Worker) Generate(ctx context.Context, dependencies asset.Parents) error
533533
}
534534
}
535535

536+
// TODO: See if we can make changes mentioned in the review.
537+
538+
dHosts := map[string]icaws.Host{}
539+
if pool.Platform.AWS.HostPlacement != nil {
540+
dHosts, err = installConfig.AWS.DedicatedHosts(ctx)
541+
if err != nil {
542+
return fmt.Errorf("failed to retrieve dedicated hosts for compute pool: %w", err)
543+
}
544+
}
545+
536546
pool.Platform.AWS = &mpool
537547
sets, err := aws.MachineSets(&aws.MachineSetInput{
538548
ClusterID: clusterID.InfraID,
@@ -543,6 +553,7 @@ func (w *Worker) Generate(ctx context.Context, dependencies asset.Parents) error
543553
Pool: &pool,
544554
Role: pool.Name,
545555
UserDataSecret: workerUserDataSecretName,
556+
Hosts: dHosts,
546557
})
547558
if err != nil {
548559
return errors.Wrap(err, "failed to create worker machine objects")

pkg/asset/machines/worker_test.go

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/openshift/installer/pkg/asset"
1212
"github.com/openshift/installer/pkg/asset/ignition/machine"
1313
"github.com/openshift/installer/pkg/asset/installconfig"
14+
icaws "github.com/openshift/installer/pkg/asset/installconfig/aws"
1415
"github.com/openshift/installer/pkg/asset/rhcos"
1516
"github.com/openshift/installer/pkg/types"
1617
awstypes "github.com/openshift/installer/pkg/types/aws"
@@ -126,36 +127,38 @@ spec:
126127
for _, tc := range cases {
127128
t.Run(tc.name, func(t *testing.T) {
128129
parents := asset.Parents{}
130+
cfg := &types.InstallConfig{
131+
ObjectMeta: metav1.ObjectMeta{
132+
Name: "test-cluster",
133+
},
134+
SSHKey: tc.key,
135+
BaseDomain: "test-domain",
136+
Platform: types.Platform{
137+
AWS: &awstypes.Platform{
138+
Region: "us-east-1",
139+
},
140+
},
141+
Compute: []types.MachinePool{
142+
{
143+
Replicas: pointer.Int64Ptr(1),
144+
Hyperthreading: tc.hyperthreading,
145+
Platform: types.MachinePoolPlatform{
146+
AWS: &awstypes.MachinePool{
147+
Zones: []string{"us-east-1a"},
148+
InstanceType: "m5.large",
149+
},
150+
},
151+
},
152+
},
153+
}
154+
icAsset := installconfig.MakeAsset(cfg)
155+
icAsset.AWS = icaws.NewMetadata(cfg.Platform.AWS.Region, cfg.Platform.AWS.VPC.Subnets, nil)
129156
parents.Add(
130157
&installconfig.ClusterID{
131158
UUID: "test-uuid",
132159
InfraID: "test-infra-id",
133160
},
134-
installconfig.MakeAsset(
135-
&types.InstallConfig{
136-
ObjectMeta: metav1.ObjectMeta{
137-
Name: "test-cluster",
138-
},
139-
SSHKey: tc.key,
140-
BaseDomain: "test-domain",
141-
Platform: types.Platform{
142-
AWS: &awstypes.Platform{
143-
Region: "us-east-1",
144-
},
145-
},
146-
Compute: []types.MachinePool{
147-
{
148-
Replicas: pointer.Int64Ptr(1),
149-
Hyperthreading: tc.hyperthreading,
150-
Platform: types.MachinePoolPlatform{
151-
AWS: &awstypes.MachinePool{
152-
Zones: []string{"us-east-1a"},
153-
InstanceType: "m5.large",
154-
},
155-
},
156-
},
157-
},
158-
}),
161+
icAsset,
159162
rhcos.MakeAsset("test-image"),
160163
(*rhcos.Release)(pointer.StringPtr("412.86.202208101040-0")),
161164
&machine.Worker{
@@ -183,34 +186,35 @@ spec:
183186

184187
func TestComputeIsNotModified(t *testing.T) {
185188
parents := asset.Parents{}
186-
installConfig := installconfig.MakeAsset(
187-
&types.InstallConfig{
188-
ObjectMeta: metav1.ObjectMeta{
189-
Name: "test-cluster",
190-
},
191-
SSHKey: "ssh-rsa: dummy-key",
192-
BaseDomain: "test-domain",
193-
Platform: types.Platform{
194-
AWS: &awstypes.Platform{
195-
Region: "us-east-1",
196-
DefaultMachinePlatform: &awstypes.MachinePool{
197-
InstanceType: "TEST_INSTANCE_TYPE",
198-
},
189+
cfg := &types.InstallConfig{
190+
ObjectMeta: metav1.ObjectMeta{
191+
Name: "test-cluster",
192+
},
193+
SSHKey: "ssh-rsa: dummy-key",
194+
BaseDomain: "test-domain",
195+
Platform: types.Platform{
196+
AWS: &awstypes.Platform{
197+
Region: "us-east-1",
198+
DefaultMachinePlatform: &awstypes.MachinePool{
199+
InstanceType: "TEST_INSTANCE_TYPE",
199200
},
200201
},
201-
Compute: []types.MachinePool{
202-
{
203-
Replicas: pointer.Int64Ptr(1),
204-
Hyperthreading: types.HyperthreadingDisabled,
205-
Platform: types.MachinePoolPlatform{
206-
AWS: &awstypes.MachinePool{
207-
Zones: []string{"us-east-1a"},
208-
InstanceType: "",
209-
},
202+
},
203+
Compute: []types.MachinePool{
204+
{
205+
Replicas: pointer.Int64Ptr(1),
206+
Hyperthreading: types.HyperthreadingDisabled,
207+
Platform: types.MachinePoolPlatform{
208+
AWS: &awstypes.MachinePool{
209+
Zones: []string{"us-east-1a"},
210+
InstanceType: "",
210211
},
211212
},
212213
},
213-
})
214+
},
215+
}
216+
installConfig := installconfig.MakeAsset(cfg)
217+
installConfig.AWS = icaws.NewMetadata(cfg.Platform.AWS.Region, cfg.Platform.AWS.VPC.Subnets, nil)
214218

215219
parents.Add(
216220
&installconfig.ClusterID{

0 commit comments

Comments
 (0)