Skip to content

Commit c35f662

Browse files
Merge pull request #8699 from jparrill/CNTRLPLANE-3026
CNTRLPLANE-3026: Decouple AWS AMI resolution for dual-stream support
2 parents 8ce9e37 + b228747 commit c35f662

5 files changed

Lines changed: 263 additions & 86 deletions

File tree

hypershift-operator/controllers/nodepool/aws.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ func resolveAWSAMI(hostedCluster *hyperv1.HostedCluster, nodePool *hyperv1.NodeP
134134
return ami, nil
135135
}
136136
// Default behavior for Linux/RHCOS AMIs
137-
ami, err := defaultNodePoolAMI(region, arch, releaseImage)
137+
// TODO(CNTRLPLANE-3553): resolve streamName via GetRHELStream once osImageStream API field is available
138+
ami, err := defaultNodePoolAMI(region, arch, "", releaseImage)
138139
if err != nil {
139140
return "", fmt.Errorf("couldn't discover an AMI for release image: %w", err)
140141
}
@@ -361,7 +362,8 @@ func (r *NodePoolReconciler) setAWSConditions(_ context.Context, nodePool *hyper
361362
})
362363
} else {
363364
// Default behavior for Linux/RHCOS AMIs
364-
ami, err := defaultNodePoolAMI(hcluster.Spec.Platform.AWS.Region, nodePool.Spec.Arch, releaseImage)
365+
// TODO(CNTRLPLANE-3553): resolve streamName via GetRHELStream once osImageStream API field is available
366+
ami, err := defaultNodePoolAMI(hcluster.Spec.Platform.AWS.Region, nodePool.Spec.Arch, "", releaseImage)
365367
if err != nil {
366368
SetStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
367369
Type: hyperv1.NodePoolValidPlatformImageType,

hypershift-operator/controllers/nodepool/aws_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,138 @@ func TestIsSpotEnabled(t *testing.T) {
10831083
}
10841084
}
10851085

1086+
func TestSetAWSConditions(t *testing.T) {
1087+
t.Parallel()
1088+
1089+
releaseImageWithStreams := &releaseinfo.ReleaseImage{
1090+
ImageStream: &v1.ImageStream{
1091+
ObjectMeta: metav1.ObjectMeta{Name: "4.17.0"},
1092+
},
1093+
StreamMetadata: &stream.Stream{
1094+
Architectures: map[string]stream.Arch{
1095+
"x86_64": {
1096+
Images: stream.Images{
1097+
Aws: &stream.AwsImage{
1098+
Regions: map[string]stream.SingleImage{
1099+
"us-east-1": {Release: "4.17.0", Image: "ami-linux-us-east-1"},
1100+
},
1101+
},
1102+
},
1103+
},
1104+
},
1105+
},
1106+
}
1107+
1108+
testCases := []struct {
1109+
name string
1110+
nodePool *hyperv1.NodePool
1111+
hostedCluster *hyperv1.HostedCluster
1112+
releaseImage *releaseinfo.ReleaseImage
1113+
expectError bool
1114+
expectedCondType string
1115+
expectedCondValue corev1.ConditionStatus
1116+
}{
1117+
{
1118+
name: "When Linux nodePool resolves AMI successfully it should set ValidPlatformImage to true",
1119+
nodePool: &hyperv1.NodePool{
1120+
Spec: hyperv1.NodePoolSpec{
1121+
Arch: hyperv1.ArchitectureAMD64,
1122+
Platform: hyperv1.NodePoolPlatform{Type: hyperv1.AWSPlatform, AWS: &hyperv1.AWSNodePoolPlatform{}},
1123+
},
1124+
},
1125+
hostedCluster: &hyperv1.HostedCluster{
1126+
Spec: hyperv1.HostedClusterSpec{
1127+
Platform: hyperv1.PlatformSpec{AWS: &hyperv1.AWSPlatformSpec{Region: "us-east-1"}},
1128+
},
1129+
Status: hyperv1.HostedClusterStatus{
1130+
Platform: &hyperv1.PlatformStatus{AWS: &hyperv1.AWSPlatformStatus{DefaultWorkerSecurityGroupID: "sg-123"}},
1131+
},
1132+
},
1133+
releaseImage: releaseImageWithStreams,
1134+
expectedCondType: string(hyperv1.NodePoolValidPlatformImageType),
1135+
expectedCondValue: corev1.ConditionTrue,
1136+
},
1137+
{
1138+
name: "When stream metadata is nil it should set ValidPlatformImage to false",
1139+
nodePool: &hyperv1.NodePool{
1140+
Spec: hyperv1.NodePoolSpec{
1141+
Arch: hyperv1.ArchitectureAMD64,
1142+
Platform: hyperv1.NodePoolPlatform{Type: hyperv1.AWSPlatform, AWS: &hyperv1.AWSNodePoolPlatform{}},
1143+
Release: hyperv1.Release{Image: "quay.io/test:4.17"},
1144+
},
1145+
},
1146+
hostedCluster: &hyperv1.HostedCluster{
1147+
Spec: hyperv1.HostedClusterSpec{
1148+
Platform: hyperv1.PlatformSpec{AWS: &hyperv1.AWSPlatformSpec{Region: "us-east-1"}},
1149+
},
1150+
},
1151+
releaseImage: &releaseinfo.ReleaseImage{
1152+
ImageStream: &v1.ImageStream{ObjectMeta: metav1.ObjectMeta{Name: "4.17.0"}},
1153+
StreamMetadata: nil,
1154+
},
1155+
expectError: true,
1156+
expectedCondType: string(hyperv1.NodePoolValidPlatformImageType),
1157+
expectedCondValue: corev1.ConditionFalse,
1158+
},
1159+
{
1160+
name: "When region has no AMI it should set ValidPlatformImage to false",
1161+
nodePool: &hyperv1.NodePool{
1162+
Spec: hyperv1.NodePoolSpec{
1163+
Arch: hyperv1.ArchitectureAMD64,
1164+
Platform: hyperv1.NodePoolPlatform{Type: hyperv1.AWSPlatform, AWS: &hyperv1.AWSNodePoolPlatform{}},
1165+
Release: hyperv1.Release{Image: "quay.io/test:4.17"},
1166+
},
1167+
},
1168+
hostedCluster: &hyperv1.HostedCluster{
1169+
Spec: hyperv1.HostedClusterSpec{
1170+
Platform: hyperv1.PlatformSpec{AWS: &hyperv1.AWSPlatformSpec{Region: "ap-nowhere-1"}},
1171+
},
1172+
},
1173+
releaseImage: releaseImageWithStreams,
1174+
expectError: true,
1175+
expectedCondType: string(hyperv1.NodePoolValidPlatformImageType),
1176+
expectedCondValue: corev1.ConditionFalse,
1177+
},
1178+
{
1179+
name: "When HostedCluster has no AWS platform it should return error",
1180+
nodePool: &hyperv1.NodePool{
1181+
Spec: hyperv1.NodePoolSpec{
1182+
Arch: hyperv1.ArchitectureAMD64,
1183+
Platform: hyperv1.NodePoolPlatform{Type: hyperv1.AWSPlatform, AWS: &hyperv1.AWSNodePoolPlatform{}},
1184+
},
1185+
},
1186+
hostedCluster: &hyperv1.HostedCluster{
1187+
Spec: hyperv1.HostedClusterSpec{
1188+
Platform: hyperv1.PlatformSpec{},
1189+
},
1190+
},
1191+
releaseImage: releaseImageWithStreams,
1192+
expectError: true,
1193+
},
1194+
}
1195+
1196+
for _, tc := range testCases {
1197+
t.Run(tc.name, func(t *testing.T) {
1198+
t.Parallel()
1199+
g := NewWithT(t)
1200+
1201+
r := &NodePoolReconciler{}
1202+
err := r.setAWSConditions(t.Context(), tc.nodePool, tc.hostedCluster, "", tc.releaseImage)
1203+
if tc.expectError {
1204+
g.Expect(err).To(HaveOccurred())
1205+
} else {
1206+
g.Expect(err).ToNot(HaveOccurred())
1207+
}
1208+
1209+
if tc.expectedCondType != "" {
1210+
cond := FindStatusCondition(tc.nodePool.Status.Conditions, tc.expectedCondType)
1211+
g.Expect(cond).ToNot(BeNil())
1212+
g.Expect(cond.Status).To(Equal(tc.expectedCondValue))
1213+
}
1214+
})
1215+
}
1216+
}
1217+
10861218
func TestResolveAWSAMI(t *testing.T) {
10871219
releaseImageWithMetadata := &releaseinfo.ReleaseImage{
10881220
ImageStream: &v1.ImageStream{
@@ -1091,6 +1223,13 @@ func TestResolveAWSAMI(t *testing.T) {
10911223
StreamMetadata: &stream.Stream{
10921224
Architectures: map[string]stream.Arch{
10931225
"x86_64": {
1226+
Images: stream.Images{
1227+
Aws: &stream.AwsImage{
1228+
Regions: map[string]stream.SingleImage{
1229+
"us-east-1": {Release: "4.17.0", Image: "ami-linux-us-east-1"},
1230+
},
1231+
},
1232+
},
10941233
RHELCoreOSExtensions: &rhcos.Extensions{
10951234
AwsWinLi: &rhcos.ReplicatedImage{
10961235
Regions: map[string]rhcos.SingleImage{
@@ -1161,6 +1300,22 @@ func TestResolveAWSAMI(t *testing.T) {
11611300
releaseImage: releaseImageWithMetadata,
11621301
expectError: true,
11631302
},
1303+
{
1304+
name: "When nodePool has default Linux type it should resolve AMI from stream metadata",
1305+
hostedCluster: &hyperv1.HostedCluster{
1306+
Spec: hyperv1.HostedClusterSpec{
1307+
Platform: hyperv1.PlatformSpec{AWS: &hyperv1.AWSPlatformSpec{Region: "us-east-1"}},
1308+
},
1309+
},
1310+
nodePool: &hyperv1.NodePool{
1311+
Spec: hyperv1.NodePoolSpec{
1312+
Arch: hyperv1.ArchitectureAMD64,
1313+
Platform: hyperv1.NodePoolPlatform{AWS: &hyperv1.AWSNodePoolPlatform{}},
1314+
},
1315+
},
1316+
releaseImage: releaseImageWithMetadata,
1317+
expectedAMI: "ami-linux-us-east-1",
1318+
},
11641319
{
11651320
name: "When nodePool has no AMI and default Linux type with nil stream metadata, it should return error",
11661321
hostedCluster: &hyperv1.HostedCluster{

hypershift-operator/controllers/nodepool/nodepool_controller.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -736,11 +736,18 @@ func isAutoscalingEnabled(nodePool *hyperv1.NodePool) bool {
736736
return nodePool.Spec.AutoScaling != nil
737737
}
738738

739-
func defaultNodePoolAMI(region string, specifiedArch string, releaseImage *releaseinfo.ReleaseImage) (string, error) {
740-
if releaseImage.StreamMetadata == nil {
741-
return "", fmt.Errorf("release image stream metadata is nil")
739+
// defaultNodePoolAMI resolves the default AWS AMI for a NodePool from release image stream metadata.
740+
// TODO(CNTRLPLANE-3553): once the osImageStream API field is available, callers should resolve
741+
// streamName via GetRHELStream and pass it here instead of hardcoding "".
742+
func defaultNodePoolAMI(region string, specifiedArch string, streamName string, releaseImage *releaseinfo.ReleaseImage) (string, error) {
743+
if releaseImage == nil {
744+
return "", fmt.Errorf("release image is nil")
742745
}
743-
arch, foundArch := releaseImage.StreamMetadata.Architectures[hyperv1.ArchAliases[specifiedArch]]
746+
streamMeta, err := releaseImage.StreamForName(streamName)
747+
if err != nil {
748+
return "", fmt.Errorf("couldn't resolve stream metadata: %w", err)
749+
}
750+
arch, foundArch := streamMeta.Architectures[hyperv1.ArchAliases[specifiedArch]]
744751
if !foundArch {
745752
return "", fmt.Errorf("couldn't find OS metadata for architecture %q", specifiedArch)
746753
}

0 commit comments

Comments
 (0)