Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/golang/mock v1.6.0
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/openshift/api v0.0.0-20251205114208-5eb46a7b4ce8
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3
github.com/openshift/library-go v0.0.0-20251107090138-0de9712313a5
github.com/openshift/machine-api-operator v0.2.1-0.20251110092458-e0af0f3f44b8
k8s.io/api v0.34.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/openshift/api v0.0.0-20251205114208-5eb46a7b4ce8 h1:kF1HhMhHSpipdHjHm92WngUCxhNC4Iy7wbF6RL739w0=
github.com/openshift/api v0.0.0-20251205114208-5eb46a7b4ce8/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3 h1:SZ8+jxtkMvpb4HDTjSAbaOyhFsw5PiWhjBog+XLY7jc=
github.com/openshift/api v0.0.0-20260213204242-d34f11c515b3/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8 h1:97rgISdT4IOmXlmEUV5Wr6d8BzzjPclzAjCARLbSlT0=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8/go.mod h1:WVJnsrbSO1J8x8KceOmv1d5CpoN34Uzsaz1O4MIOKJI=
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d h1:+sqUThLi/lmgT5/scmmjnS6+RZFtbdxRAscNfCPyLPI=
Expand Down
142 changes: 142 additions & 0 deletions pkg/actuators/machine/dedicatedhosts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package machine

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
awsclient "github.com/openshift/machine-api-provider-aws/pkg/client"
"k8s.io/klog/v2"
)

const (
// AllocationStrategyDynamic represents the dynamic allocation strategy constant.
AllocationStrategyDynamic = machinev1beta1.AllocationStrategy("Dynamic")
// AllocationStrategyUserProvided represents the user-provided allocation strategy constant.
AllocationStrategyUserProvided = machinev1beta1.AllocationStrategy("UserProvided")
)

// allocateDedicatedHost allocates a new dedicated host for the given instance type in the specified availability zone.
// It applies any tags specified in the DynamicHostAllocation configuration.
func allocateDedicatedHost(client awsclient.Client, instanceType, availabilityZone string, tags []*ec2.Tag, machineName string) (string, error) {
klog.Infof("Allocating dedicated host for instance type %s in availability zone %s for machine %s", instanceType, availabilityZone, machineName)

allocateInput := &ec2.AllocateHostsInput{
InstanceType: aws.String(instanceType),
AvailabilityZone: aws.String(availabilityZone),
Quantity: aws.Int64(1),
AutoPlacement: aws.String("off"), // Disable auto-placement to ensure 1:1 mapping
}

// Add tags if provided
if len(tags) > 0 {
var tagSpecs []*ec2.TagSpecification
tagSpecs = append(tagSpecs, &ec2.TagSpecification{
ResourceType: aws.String("dedicated-host"),
Tags: tags,
})
allocateInput.TagSpecifications = tagSpecs
}

output, err := client.AllocateHosts(allocateInput)
if err != nil {
klog.Errorf("Failed to allocate dedicated host: %v", err)
return "", fmt.Errorf("failed to allocate dedicated host: %w", err)
}

if len(output.HostIds) == 0 {
return "", fmt.Errorf("no host IDs returned from AllocateHosts")
}

hostID := aws.StringValue(output.HostIds[0])
klog.Infof("Successfully allocated dedicated host %s for machine %s", hostID, machineName)
return hostID, nil
}

// releaseDedicatedHost releases the dedicated host with the given ID.
func releaseDedicatedHost(client awsclient.Client, hostID, machineName string) error {
klog.Infof("Releasing dedicated host %s for machine %s", hostID, machineName)

releaseInput := &ec2.ReleaseHostsInput{
HostIds: []*string{aws.String(hostID)},
}

output, err := client.ReleaseHosts(releaseInput)
if err != nil {
klog.Errorf("Failed to release dedicated host %s: %v", hostID, err)
return fmt.Errorf("failed to release dedicated host %s: %w", hostID, err)
}

// Check if there were any failures
if len(output.Unsuccessful) > 0 {
klog.Errorf("Failed to release dedicated host %s: %v", hostID, aws.StringValue(output.Unsuccessful[0].Error.Message))
return fmt.Errorf("failed to release dedicated host %s: %s", hostID, aws.StringValue(output.Unsuccessful[0].Error.Message))
}

klog.Infof("Successfully released dedicated host %s for machine %s", hostID, machineName)
return nil
}

// shouldAllocateDedicatedHost checks if a dedicated host should be allocated based on the placement configuration.
func shouldAllocateDedicatedHost(placement *machinev1beta1.Placement) bool {
if placement == nil || placement.Host == nil || placement.Host.DedicatedHost == nil {
return false
}

// If AllocationStrategy is nil, default is UserProvided, so we don't allocate
if placement.Host.DedicatedHost.AllocationStrategy == nil {
return false
}

return *placement.Host.DedicatedHost.AllocationStrategy == AllocationStrategyDynamic
}

// getDedicatedHostID returns the dedicated host ID from the placement configuration if it's user-provided.
func getDedicatedHostID(placement *machinev1beta1.Placement) string {
if placement == nil || placement.Host == nil || placement.Host.DedicatedHost == nil {
return ""
}

// If AllocationStrategy is nil or UserProvided, return the ID
if placement.Host.DedicatedHost.AllocationStrategy == nil ||
*placement.Host.DedicatedHost.AllocationStrategy == AllocationStrategyUserProvided {
return placement.Host.DedicatedHost.ID
}

return ""
}

// getDynamicHostTags returns the tags to apply to a dynamically allocated dedicated host.
func getDynamicHostTags(placement *machinev1beta1.Placement) []machinev1beta1.TagSpecification {
if placement == nil || placement.Host == nil || placement.Host.DedicatedHost == nil ||
placement.Host.DedicatedHost.DynamicHostAllocation == nil ||
placement.Host.DedicatedHost.DynamicHostAllocation.Tags == nil {
return nil
}

return *placement.Host.DedicatedHost.DynamicHostAllocation.Tags
}

// getAllocatedHostIDFromStatus returns the dynamically allocated host ID from the provider status.
func getAllocatedHostIDFromStatus(providerStatus *machinev1beta1.AWSMachineProviderStatus) string {
if providerStatus == nil || providerStatus.DedicatedHost == nil || providerStatus.DedicatedHost.ID == "" {
return ""
}
return providerStatus.DedicatedHost.ID
}

// setAllocatedHostIDInStatus sets the dynamically allocated host ID in the provider status.
func setAllocatedHostIDInStatus(providerStatus *machinev1beta1.AWSMachineProviderStatus, hostID string) {
if providerStatus.DedicatedHost == nil {
providerStatus.DedicatedHost = &machinev1beta1.DedicatedHostStatus{}
}
providerStatus.DedicatedHost.ID = hostID
}

// clearAllocatedHostIDInStatus clears the dynamically allocated host ID from the provider status.
func clearAllocatedHostIDInStatus(providerStatus *machinev1beta1.AWSMachineProviderStatus) {
if providerStatus.DedicatedHost != nil {
providerStatus.DedicatedHost.ID = ""
}
}
Loading