Skip to content

Commit e5b01de

Browse files
authored
🐛 add marketType field validation (#5374)
* Add spotMarketRequest on marketType spot * Add CEL validation * update vendor files * added validation * Updated the comments
1 parent c8aae16 commit e5b01de

7 files changed

+114
-0
lines changed

api/v1beta2/awsmachine_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const (
6464
)
6565

6666
// AWSMachineSpec defines the desired state of an Amazon EC2 instance.
67+
// +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.marketType) || self.marketType != 'Spot'",message="capacityReservationId may not be set when marketType is Spot"
68+
// +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.spotMarketOptions)",message="capacityReservationId cannot be set when spotMarketOptions is specified"
6769
type AWSMachineSpec struct {
6870
// ProviderID is the unique identifier as specified by the cloud provider.
6971
ProviderID *string `json:"providerID,omitempty"`

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,13 @@ spec:
11041104
required:
11051105
- instanceType
11061106
type: object
1107+
x-kubernetes-validations:
1108+
- message: capacityReservationId may not be set when marketType is Spot
1109+
rule: '!has(self.capacityReservationId) || !has(self.marketType) ||
1110+
self.marketType != ''Spot'''
1111+
- message: capacityReservationId cannot be set when spotMarketOptions
1112+
is specified
1113+
rule: '!has(self.capacityReservationId) || !has(self.spotMarketOptions)'
11071114
status:
11081115
description: AWSMachineStatus defines the observed state of AWSMachine.
11091116
properties:

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,14 @@ spec:
10451045
required:
10461046
- instanceType
10471047
type: object
1048+
x-kubernetes-validations:
1049+
- message: capacityReservationId may not be set when marketType
1050+
is Spot
1051+
rule: '!has(self.capacityReservationId) || !has(self.marketType)
1052+
|| self.marketType != ''Spot'''
1053+
- message: capacityReservationId cannot be set when spotMarketOptions
1054+
is specified
1055+
rule: '!has(self.capacityReservationId) || !has(self.spotMarketOptions)'
10481056
required:
10491057
- spec
10501058
type: object

exp/api/v1beta2/awsmachinepool_webhook.go

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta2
1818

1919
import (
20+
"fmt"
2021
"time"
2122

2223
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -201,6 +202,18 @@ func (r *AWSMachinePool) validateInstanceMarketType() field.ErrorList {
201202
if r.Spec.AWSLaunchTemplate.MarketType == v1beta2.MarketTypeCapacityBlock && r.Spec.AWSLaunchTemplate.CapacityReservationID == nil {
202203
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.capacityReservationID"), "is required when CapacityBlock is provided"))
203204
}
205+
switch r.Spec.AWSLaunchTemplate.MarketType {
206+
case "", v1beta2.MarketTypeOnDemand, v1beta2.MarketTypeSpot, v1beta2.MarketTypeCapacityBlock:
207+
default:
208+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.awsLaunchTemplate.MarketType"), r.Spec.AWSLaunchTemplate.MarketType, fmt.Sprintf("Valid values are: %s, %s, %s and omitted", v1beta2.MarketTypeOnDemand, v1beta2.MarketTypeSpot, v1beta2.MarketTypeCapacityBlock)))
209+
}
210+
if r.Spec.AWSLaunchTemplate.MarketType == v1beta2.MarketTypeSpot && r.Spec.AWSLaunchTemplate.CapacityReservationID != nil {
211+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.marketType"), "cannot be set to 'Spot' when CapacityReservationID is specified"))
212+
}
213+
214+
if r.Spec.AWSLaunchTemplate.CapacityReservationID != nil && r.Spec.AWSLaunchTemplate.SpotMarketOptions != nil {
215+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.spotMarketOptions"), "cannot be set to when CapacityReservationID is specified"))
216+
}
204217

205218
return allErrs
206219
}

exp/api/v1beta2/awsmachinepool_webhook_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,53 @@ func TestAWSMachinePoolValidateCreate(t *testing.T) {
174174
},
175175
wantErr: true,
176176
},
177+
{
178+
name: "with invalid MarketType provided",
179+
pool: &AWSMachinePool{
180+
Spec: AWSMachinePoolSpec{
181+
AWSLaunchTemplate: AWSLaunchTemplate{
182+
MarketType: "invalid",
183+
},
184+
},
185+
},
186+
wantErr: true,
187+
},
188+
{
189+
name: "with MarketType empty value provided",
190+
pool: &AWSMachinePool{
191+
Spec: AWSMachinePoolSpec{
192+
AWSLaunchTemplate: AWSLaunchTemplate{
193+
MarketType: "",
194+
},
195+
},
196+
},
197+
wantErr: false,
198+
},
199+
{
200+
name: "with MarketType Spot and CapacityReservationID value provided",
201+
pool: &AWSMachinePool{
202+
Spec: AWSMachinePoolSpec{
203+
AWSLaunchTemplate: AWSLaunchTemplate{
204+
MarketType: infrav1.MarketTypeSpot,
205+
CapacityReservationID: aws.String("cr-123"),
206+
},
207+
},
208+
},
209+
wantErr: true,
210+
},
211+
{
212+
name: "with CapacityReservationID and SpotMarketOptions value provided",
213+
pool: &AWSMachinePool{
214+
Spec: AWSMachinePoolSpec{
215+
AWSLaunchTemplate: AWSLaunchTemplate{
216+
SpotMarketOptions: &infrav1.SpotMarketOptions{},
217+
CapacityReservationID: aws.String("cr-123"),
218+
},
219+
},
220+
},
221+
wantErr: true,
222+
},
223+
177224
{
178225
name: "invalid, MarketType set to MarketTypeCapacityBlock and spotMarketOptions are specified",
179226
pool: &AWSMachinePool{

pkg/cloud/services/ec2/instances.go

+8
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,10 @@ func getInstanceMarketOptionsRequest(i *infrav1.Instance) (*ec2.InstanceMarketOp
11541154
return nil, errors.New("can't create spot capacity-blocks, remove spot market request")
11551155
}
11561156

1157+
if (i.MarketType == infrav1.MarketTypeSpot || i.SpotMarketOptions != nil) && i.CapacityReservationID != nil {
1158+
return nil, errors.New("unable to generate marketOptions for spot instance, capacityReservationID is incompatible with marketType spot and spotMarketOptions")
1159+
}
1160+
11571161
// Infer MarketType if not explicitly set
11581162
if i.SpotMarketOptions != nil && i.MarketType == "" {
11591163
i.MarketType = infrav1.MarketTypeSpot
@@ -1163,6 +1167,10 @@ func getInstanceMarketOptionsRequest(i *infrav1.Instance) (*ec2.InstanceMarketOp
11631167
i.MarketType = infrav1.MarketTypeOnDemand
11641168
}
11651169

1170+
if i.MarketType == infrav1.MarketTypeSpot && i.SpotMarketOptions == nil {
1171+
i.SpotMarketOptions = &infrav1.SpotMarketOptions{}
1172+
}
1173+
11661174
switch i.MarketType {
11671175
case infrav1.MarketTypeCapacityBlock:
11681176
if i.CapacityReservationID == nil {

pkg/cloud/services/ec2/instances_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -5770,6 +5770,35 @@ func TestGetInstanceMarketOptionsRequest(t *testing.T) {
57705770
},
57715771
expectedError: nil,
57725772
},
5773+
{
5774+
name: "with marketType Spot specified",
5775+
instance: &infrav1.Instance{
5776+
MarketType: infrav1.MarketTypeSpot,
5777+
},
5778+
expectedRequest: &ec2.InstanceMarketOptionsRequest{
5779+
MarketType: aws.String(ec2.MarketTypeSpot),
5780+
SpotOptions: &ec2.SpotMarketOptions{
5781+
InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate),
5782+
SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime),
5783+
},
5784+
},
5785+
},
5786+
{
5787+
name: "with marketType Spot and capacityRerservationID specified",
5788+
instance: &infrav1.Instance{
5789+
MarketType: infrav1.MarketTypeSpot,
5790+
CapacityReservationID: mockCapacityReservationID,
5791+
},
5792+
expectedError: errors.Errorf("unable to generate marketOptions for spot instance, capacityReservationID is incompatible with marketType spot and spotMarketOptions"),
5793+
},
5794+
{
5795+
name: "with spotMarketOptions and capacityRerservationID specified",
5796+
instance: &infrav1.Instance{
5797+
SpotMarketOptions: &infrav1.SpotMarketOptions{},
5798+
CapacityReservationID: mockCapacityReservationID,
5799+
},
5800+
expectedError: errors.Errorf("unable to generate marketOptions for spot instance, capacityReservationID is incompatible with marketType spot and spotMarketOptions"),
5801+
},
57735802
{
57745803
name: "with an empty MaxPrice specified",
57755804
instance: &infrav1.Instance{

0 commit comments

Comments
 (0)