Skip to content

Commit ac6111f

Browse files
Merge pull request #10079 from vr4manta/SPLAT-2172
SPLAT-2172: AWS dedicate host support
2 parents bb827bf + e6282c4 commit ac6111f

21 files changed

Lines changed: 1268 additions & 56 deletions

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,56 @@ spec:
211211
- AMDEncryptedVirtualizationNestedPaging
212212
type: string
213213
type: object
214+
hostPlacement:
215+
description: |-
216+
hostPlacement configures placement on AWS Dedicated Hosts. This allows admins to assign instances to specific host
217+
for a variety of needs including for regulatory compliance, to leverage existing per-socket or per-core software licenses (BYOL),
218+
and to gain visibility and control over instance placement on a physical server.
219+
When omitted, the instance is not constrained to a dedicated host.
220+
properties:
221+
affinity:
222+
description: |-
223+
affinity specifies the affinity setting for the instance.
224+
Allowed values are AnyAvailable and DedicatedHost.
225+
When Affinity is set to DedicatedHost, an instance started onto a specific host always restarts on the same host if stopped. In this scenario, the `dedicatedHost` field must be set.
226+
When Affinity is set to AnyAvailable, and you stop and restart the instance, it can be restarted on any available host.
227+
enum:
228+
- DedicatedHost
229+
- AnyAvailable
230+
type: string
231+
dedicatedHost:
232+
description: |-
233+
dedicatedHost specifies the exact host that an instance should be restarted on if stopped.
234+
dedicatedHost is required when 'affinity' is set to DedicatedHost, and forbidden otherwise.
235+
items:
236+
description: DedicatedHost represents the configuration
237+
for the usage of dedicated host.
238+
properties:
239+
id:
240+
description: |-
241+
id identifies the AWS Dedicated Host on which the instance must run.
242+
The value must start with "h-" followed by 17 lowercase hexadecimal characters (0-9 and a-f).
243+
Must be exactly 19 characters in length.
244+
maxLength: 19
245+
minLength: 19
246+
type: string
247+
x-kubernetes-validations:
248+
- message: hostID must start with 'h-' followed
249+
by 17 lowercase hexadecimal characters (0-9
250+
and a-f)
251+
rule: self.matches('^h-[0-9a-f]{17}$')
252+
required:
253+
- id
254+
type: object
255+
type: array
256+
required:
257+
- affinity
258+
type: object
259+
x-kubernetes-validations:
260+
- message: dedicatedHost is required when affinity is DedicatedHost,
261+
and forbidden otherwise
262+
rule: 'has(self.affinity) && self.affinity == ''DedicatedHost''
263+
? has(self.dedicatedHost) : !has(self.dedicatedHost)'
214264
iamProfile:
215265
description: |-
216266
IAMProfile is the name of the IAM instance profile to use for the machine.
@@ -1783,6 +1833,56 @@ spec:
17831833
- AMDEncryptedVirtualizationNestedPaging
17841834
type: string
17851835
type: object
1836+
hostPlacement:
1837+
description: |-
1838+
hostPlacement configures placement on AWS Dedicated Hosts. This allows admins to assign instances to specific host
1839+
for a variety of needs including for regulatory compliance, to leverage existing per-socket or per-core software licenses (BYOL),
1840+
and to gain visibility and control over instance placement on a physical server.
1841+
When omitted, the instance is not constrained to a dedicated host.
1842+
properties:
1843+
affinity:
1844+
description: |-
1845+
affinity specifies the affinity setting for the instance.
1846+
Allowed values are AnyAvailable and DedicatedHost.
1847+
When Affinity is set to DedicatedHost, an instance started onto a specific host always restarts on the same host if stopped. In this scenario, the `dedicatedHost` field must be set.
1848+
When Affinity is set to AnyAvailable, and you stop and restart the instance, it can be restarted on any available host.
1849+
enum:
1850+
- DedicatedHost
1851+
- AnyAvailable
1852+
type: string
1853+
dedicatedHost:
1854+
description: |-
1855+
dedicatedHost specifies the exact host that an instance should be restarted on if stopped.
1856+
dedicatedHost is required when 'affinity' is set to DedicatedHost, and forbidden otherwise.
1857+
items:
1858+
description: DedicatedHost represents the configuration
1859+
for the usage of dedicated host.
1860+
properties:
1861+
id:
1862+
description: |-
1863+
id identifies the AWS Dedicated Host on which the instance must run.
1864+
The value must start with "h-" followed by 17 lowercase hexadecimal characters (0-9 and a-f).
1865+
Must be exactly 19 characters in length.
1866+
maxLength: 19
1867+
minLength: 19
1868+
type: string
1869+
x-kubernetes-validations:
1870+
- message: hostID must start with 'h-' followed
1871+
by 17 lowercase hexadecimal characters (0-9
1872+
and a-f)
1873+
rule: self.matches('^h-[0-9a-f]{17}$')
1874+
required:
1875+
- id
1876+
type: object
1877+
type: array
1878+
required:
1879+
- affinity
1880+
type: object
1881+
x-kubernetes-validations:
1882+
- message: dedicatedHost is required when affinity is DedicatedHost,
1883+
and forbidden otherwise
1884+
rule: 'has(self.affinity) && self.affinity == ''DedicatedHost''
1885+
? has(self.dedicatedHost) : !has(self.dedicatedHost)'
17861886
iamProfile:
17871887
description: |-
17881888
IAMProfile is the name of the IAM instance profile to use for the machine.
@@ -3295,6 +3395,56 @@ spec:
32953395
- AMDEncryptedVirtualizationNestedPaging
32963396
type: string
32973397
type: object
3398+
hostPlacement:
3399+
description: |-
3400+
hostPlacement configures placement on AWS Dedicated Hosts. This allows admins to assign instances to specific host
3401+
for a variety of needs including for regulatory compliance, to leverage existing per-socket or per-core software licenses (BYOL),
3402+
and to gain visibility and control over instance placement on a physical server.
3403+
When omitted, the instance is not constrained to a dedicated host.
3404+
properties:
3405+
affinity:
3406+
description: |-
3407+
affinity specifies the affinity setting for the instance.
3408+
Allowed values are AnyAvailable and DedicatedHost.
3409+
When Affinity is set to DedicatedHost, an instance started onto a specific host always restarts on the same host if stopped. In this scenario, the `dedicatedHost` field must be set.
3410+
When Affinity is set to AnyAvailable, and you stop and restart the instance, it can be restarted on any available host.
3411+
enum:
3412+
- DedicatedHost
3413+
- AnyAvailable
3414+
type: string
3415+
dedicatedHost:
3416+
description: |-
3417+
dedicatedHost specifies the exact host that an instance should be restarted on if stopped.
3418+
dedicatedHost is required when 'affinity' is set to DedicatedHost, and forbidden otherwise.
3419+
items:
3420+
description: DedicatedHost represents the configuration
3421+
for the usage of dedicated host.
3422+
properties:
3423+
id:
3424+
description: |-
3425+
id identifies the AWS Dedicated Host on which the instance must run.
3426+
The value must start with "h-" followed by 17 lowercase hexadecimal characters (0-9 and a-f).
3427+
Must be exactly 19 characters in length.
3428+
maxLength: 19
3429+
minLength: 19
3430+
type: string
3431+
x-kubernetes-validations:
3432+
- message: hostID must start with 'h-' followed
3433+
by 17 lowercase hexadecimal characters (0-9
3434+
and a-f)
3435+
rule: self.matches('^h-[0-9a-f]{17}$')
3436+
required:
3437+
- id
3438+
type: object
3439+
type: array
3440+
required:
3441+
- affinity
3442+
type: object
3443+
x-kubernetes-validations:
3444+
- message: dedicatedHost is required when affinity is DedicatedHost,
3445+
and forbidden otherwise
3446+
rule: 'has(self.affinity) && self.affinity == ''DedicatedHost''
3447+
? has(self.dedicatedHost) : !has(self.dedicatedHost)'
32983448
iamProfile:
32993449
description: |-
33003450
IAMProfile is the name of the IAM instance profile to use for the machine.
@@ -4995,6 +5145,56 @@ spec:
49955145
- AMDEncryptedVirtualizationNestedPaging
49965146
type: string
49975147
type: object
5148+
hostPlacement:
5149+
description: |-
5150+
hostPlacement configures placement on AWS Dedicated Hosts. This allows admins to assign instances to specific host
5151+
for a variety of needs including for regulatory compliance, to leverage existing per-socket or per-core software licenses (BYOL),
5152+
and to gain visibility and control over instance placement on a physical server.
5153+
When omitted, the instance is not constrained to a dedicated host.
5154+
properties:
5155+
affinity:
5156+
description: |-
5157+
affinity specifies the affinity setting for the instance.
5158+
Allowed values are AnyAvailable and DedicatedHost.
5159+
When Affinity is set to DedicatedHost, an instance started onto a specific host always restarts on the same host if stopped. In this scenario, the `dedicatedHost` field must be set.
5160+
When Affinity is set to AnyAvailable, and you stop and restart the instance, it can be restarted on any available host.
5161+
enum:
5162+
- DedicatedHost
5163+
- AnyAvailable
5164+
type: string
5165+
dedicatedHost:
5166+
description: |-
5167+
dedicatedHost specifies the exact host that an instance should be restarted on if stopped.
5168+
dedicatedHost is required when 'affinity' is set to DedicatedHost, and forbidden otherwise.
5169+
items:
5170+
description: DedicatedHost represents the configuration
5171+
for the usage of dedicated host.
5172+
properties:
5173+
id:
5174+
description: |-
5175+
id identifies the AWS Dedicated Host on which the instance must run.
5176+
The value must start with "h-" followed by 17 lowercase hexadecimal characters (0-9 and a-f).
5177+
Must be exactly 19 characters in length.
5178+
maxLength: 19
5179+
minLength: 19
5180+
type: string
5181+
x-kubernetes-validations:
5182+
- message: hostID must start with 'h-' followed
5183+
by 17 lowercase hexadecimal characters (0-9
5184+
and a-f)
5185+
rule: self.matches('^h-[0-9a-f]{17}$')
5186+
required:
5187+
- id
5188+
type: object
5189+
type: array
5190+
required:
5191+
- affinity
5192+
type: object
5193+
x-kubernetes-validations:
5194+
- message: dedicatedHost is required when affinity is DedicatedHost,
5195+
and forbidden otherwise
5196+
rule: 'has(self.affinity) && self.affinity == ''DedicatedHost''
5197+
? has(self.dedicatedHost) : !has(self.dedicatedHost)'
49985198
iamProfile:
49995199
description: |-
50005200
IAMProfile is the name of the IAM instance profile to use for the machine.

pkg/asset/cluster/aws/aws.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ func PreTerraform(ctx context.Context, clusterID string, installConfig *installc
4444
return err
4545
}
4646

47+
if err := tagSharedDedicatedHosts(ctx, clusterID, installConfig); err != nil {
48+
return err
49+
}
50+
4751
if err := tagSharedIAMRoles(ctx, clusterID, installConfig); err != nil {
4852
return err
4953
}
@@ -244,6 +248,50 @@ func tagSharedIAMProfiles(ctx context.Context, clusterID string, installConfig *
244248
return nil
245249
}
246250

251+
// tagSharedDedicatedHosts tags users BYO dedicated hosts so they are not destroyed by the Installer.
252+
func tagSharedDedicatedHosts(ctx context.Context, clusterID string, installConfig *installconfig.InstallConfig) error {
253+
var dhNames []string
254+
255+
// DH is only allowed to be created in "worker" pool at this time. No need to check default platform.
256+
for _, compute := range installConfig.Config.Compute {
257+
if compute.Name == "worker" {
258+
mpool := awstypes.MachinePool{}
259+
mpool.Set(installConfig.Config.AWS.DefaultMachinePlatform)
260+
mpool.Set(compute.Platform.AWS)
261+
if mpool.HostPlacement != nil && mpool.HostPlacement.DedicatedHost != nil {
262+
for _, name := range mpool.HostPlacement.DedicatedHost {
263+
dhNames = append(dhNames, name.ID)
264+
}
265+
}
266+
}
267+
}
268+
269+
if len(dhNames) == 0 {
270+
return nil
271+
}
272+
273+
logrus.Debugf("Tagging shared dedicated hosts: %v", dhNames)
274+
275+
tagKey, tagValue := sharedTag(clusterID)
276+
277+
ec2Client, err := awsconfig.NewEC2Client(ctx, awsconfig.EndpointOptions{
278+
Region: installConfig.Config.Platform.AWS.Region,
279+
Endpoints: installConfig.Config.Platform.AWS.ServiceEndpoints,
280+
})
281+
if err != nil {
282+
return fmt.Errorf("failed to create EC2 client: %w", err)
283+
}
284+
285+
if _, err = ec2Client.CreateTags(ctx, &ec2.CreateTagsInput{
286+
Resources: dhNames,
287+
Tags: []ec2types.Tag{{Key: &tagKey, Value: &tagValue}},
288+
}); err != nil {
289+
return errors.Wrap(err, "could not add tags to dedicated host")
290+
}
291+
292+
return nil
293+
}
294+
247295
func sharedTag(clusterID string) (string, string) {
248296
return fmt.Sprintf("kubernetes.io/cluster/%s", clusterID), "shared"
249297
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/aws/aws-sdk-go-v2/aws"
8+
"github.com/aws/aws-sdk-go-v2/service/ec2"
9+
"github.com/sirupsen/logrus"
10+
)
11+
12+
// Host holds metadata for a dedicated host.
13+
type Host struct {
14+
// ID the ID of the host.
15+
ID string
16+
// Zone the zone the host belongs to.
17+
Zone string
18+
// Tags is the map of the Host's tags.
19+
Tags Tags
20+
}
21+
22+
// dedicatedHosts retrieves a list of dedicated hosts and returns them in a map keyed by the host ID.
23+
func dedicatedHosts(ctx context.Context, client *ec2.Client, hosts []string) (map[string]Host, error) {
24+
hostsByID := map[string]Host{}
25+
26+
input := &ec2.DescribeHostsInput{}
27+
if len(hosts) > 0 {
28+
input.HostIds = hosts
29+
}
30+
31+
paginator := ec2.NewDescribeHostsPaginator(client, input)
32+
for paginator.HasMorePages() {
33+
page, err := paginator.NextPage(ctx)
34+
if err != nil {
35+
return nil, fmt.Errorf("fetching dedicated hosts: %w", err)
36+
}
37+
38+
for _, host := range page.Hosts {
39+
id := aws.ToString(host.HostId)
40+
if id == "" {
41+
// Skip entries lacking an ID (should not happen)
42+
continue
43+
}
44+
45+
logrus.Debugf("Found dedicated host: %s", id)
46+
hostsByID[id] = Host{
47+
ID: id,
48+
Zone: aws.ToString(host.AvailabilityZone),
49+
Tags: FromAWSTags(host.Tags),
50+
}
51+
}
52+
}
53+
54+
return hostsByID, nil
55+
}

pkg/asset/installconfig/aws/metadata.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Metadata struct {
2525
instanceTypes map[string]InstanceType
2626
images map[string]ImageInfo
2727

28+
Hosts map[string]Host
2829
Region string `json:"region,omitempty"`
2930
ProvidedSubnets []typesaws.Subnet `json:"subnets,omitempty"`
3031
Services []typesaws.ServiceEndpoint `json:"services,omitempty"`
@@ -406,3 +407,29 @@ func (m *Metadata) Images(ctx context.Context, amiID string) (ImageInfo, error)
406407

407408
return imageInfo, nil
408409
}
410+
411+
// DedicatedHosts retrieves all hosts available for use to verify against this installation for configured region.
412+
func (m *Metadata) DedicatedHosts(ctx context.Context, hosts []typesaws.DedicatedHost) (map[string]Host, error) {
413+
m.mutex.Lock()
414+
defer m.mutex.Unlock()
415+
416+
// Create array of host IDs for client
417+
hostIDs := make([]string, len(hosts))
418+
for idx, host := range hosts {
419+
hostIDs[idx] = host.ID
420+
}
421+
422+
if len(m.Hosts) == 0 {
423+
client, err := m.EC2Client(ctx)
424+
if err != nil {
425+
return nil, err
426+
}
427+
428+
m.Hosts, err = dedicatedHosts(ctx, client, hostIDs)
429+
if err != nil {
430+
return nil, fmt.Errorf("error listing dedicated hosts: %w", err)
431+
}
432+
}
433+
434+
return m.Hosts, nil
435+
}

0 commit comments

Comments
 (0)