Skip to content

Commit

Permalink
Additional Worker Node Pools on Preview
Browse files Browse the repository at this point in the history
  • Loading branch information
KsaweryZietara committed Dec 27, 2024
1 parent f28d608 commit 02c7833
Show file tree
Hide file tree
Showing 42 changed files with 368 additions and 14 deletions.
16 changes: 12 additions & 4 deletions common/runtime/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ type ProvisioningParametersDTO struct {
ShootName string `json:"shootName,omitempty"`
ShootDomain string `json:"shootDomain,omitempty"`

OIDC *OIDCConfigDTO `json:"oidc,omitempty"`
Networking *NetworkingDTO `json:"networking,omitempty"`
Modules *ModulesDTO `json:"modules,omitempty"`
ShootAndSeedSameRegion *bool `json:"shootAndSeedSameRegion,omitempty"`
OIDC *OIDCConfigDTO `json:"oidc,omitempty"`
Networking *NetworkingDTO `json:"networking,omitempty"`
Modules *ModulesDTO `json:"modules,omitempty"`
ShootAndSeedSameRegion *bool `json:"shootAndSeedSameRegion,omitempty"`
AdditionalWorkerNodePools []AdditionalWorkerNodePool `json:"additionalWorkerNodePools,omitempty"`
}

type AutoScalerParameters struct {
Expand Down Expand Up @@ -386,3 +387,10 @@ type ModuleDTO struct {
Channel Channel `json:"channel,omitempty" yaml:"channel,omitempty"`
CustomResourcePolicy CustomResourcePolicy `json:"customResourcePolicy,omitempty" yaml:"customResourcePolicy,omitempty"`
}

type AdditionalWorkerNodePool struct {
AutoScalerParameters `json:",inline"`

Name string `json:"name"`
MachineType *string `json:"machineType,omitempty"`
}
9 changes: 9 additions & 0 deletions internal/broker/instance_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,15 @@ func (b *ProvisionEndpoint) validateAndExtract(details domain.ProvisionDetails,
if err := parameters.AutoScalerParameters.Validate(autoscalerMin, autoscalerMax); err != nil {
return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusUnprocessableEntity, err.Error())
}

if IsPreviewPlan(details.PlanID) {
for _, workerNodePool := range parameters.AdditionalWorkerNodePools {
if err := workerNodePool.AutoScalerParameters.Validate(autoscalerMin, autoscalerMax); err != nil {
return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusUnprocessableEntity, err.Error())
}
}
}

if parameters.OIDC.IsProvided() {
if err := parameters.OIDC.Validate(); err != nil {
return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusUnprocessableEntity, err.Error())
Expand Down
159 changes: 159 additions & 0 deletions internal/broker/instance_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,165 @@ func TestProvision_Provision(t *testing.T) {
// then
require.EqualError(t, err, "while validating input parameters: region: region must be one of the following: \"me-central2\"")
})

t.Run("Should pass with additional worker node pools", func(t *testing.T) {
// given
memoryStorage := storage.NewMemoryStorage()

queue := &automock.Queue{}
queue.On("Add", mock.AnythingOfType("string"))

factoryBuilder := &automock.PlanValidator{}
factoryBuilder.On("IsPlanSupport", broker.PreviewPlanID).Return(true)

planDefaults := func(planID string, platformProvider pkg.CloudProvider, provider *pkg.CloudProvider) (*gqlschema.ClusterConfigInput, error) {
return &gqlschema.ClusterConfigInput{}, nil
}
kcBuilder := &kcMock.KcBuilder{}
kcBuilder.On("GetServerURL", "").Return("", fmt.Errorf("error"))
// #create provisioner endpoint
provisionEndpoint := broker.NewProvision(
broker.Config{
EnablePlans: []string{"preview"},
URL: brokerURL,
OnlySingleTrialPerGA: true,
EnableKubeconfigURLLabel: true,
},
gardener.Config{Project: "test", ShootDomain: "example.com", DNSProviders: fixDNSProviders()},
memoryStorage.Operations(),
memoryStorage.Instances(),
memoryStorage.InstancesArchived(),
queue,
factoryBuilder,
broker.PlansConfig{},
planDefaults,
log,
dashboardConfig,
kcBuilder,
whitelist.Set{},
&broker.OneForAllConvergedCloudRegionsProvider{},
)

additionalWorkerNodePools := `[{"name": "name-1", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}, {"name": "name-2", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}]`

// when
_, err := provisionEndpoint.Provision(fixRequestContext(t, "cf-sa30"), instanceID, domain.ProvisionDetails{
ServiceID: serviceID,
PlanID: broker.PreviewPlanID,
RawParameters: json.RawMessage(fmt.Sprintf(`{"name": "%s", "region": "%s","additionalWorkerNodePools": %s }`, clusterName, "eu-central-1", additionalWorkerNodePools)),
RawContext: json.RawMessage(fmt.Sprintf(`{"globalaccount_id": "%s", "subaccount_id": "%s", "user_id": "%s"}`, "any-global-account-id", subAccountID, "[email protected]")),
}, true)
t.Logf("%+v\n", *provisionEndpoint)

// then
require.NoError(t, err)
})

t.Run("Should pass with empty additional worker node pools", func(t *testing.T) {
// given
memoryStorage := storage.NewMemoryStorage()

queue := &automock.Queue{}
queue.On("Add", mock.AnythingOfType("string"))

factoryBuilder := &automock.PlanValidator{}
factoryBuilder.On("IsPlanSupport", broker.PreviewPlanID).Return(true)

planDefaults := func(planID string, platformProvider pkg.CloudProvider, provider *pkg.CloudProvider) (*gqlschema.ClusterConfigInput, error) {
return &gqlschema.ClusterConfigInput{}, nil
}
kcBuilder := &kcMock.KcBuilder{}
kcBuilder.On("GetServerURL", "").Return("", fmt.Errorf("error"))
// #create provisioner endpoint
provisionEndpoint := broker.NewProvision(
broker.Config{
EnablePlans: []string{"preview"},
URL: brokerURL,
OnlySingleTrialPerGA: true,
EnableKubeconfigURLLabel: true,
},
gardener.Config{Project: "test", ShootDomain: "example.com", DNSProviders: fixDNSProviders()},
memoryStorage.Operations(),
memoryStorage.Instances(),
memoryStorage.InstancesArchived(),
queue,
factoryBuilder,
broker.PlansConfig{},
planDefaults,
log,
dashboardConfig,
kcBuilder,
whitelist.Set{},
&broker.OneForAllConvergedCloudRegionsProvider{},
)

additionalWorkerNodePools := "[]"

// when
_, err := provisionEndpoint.Provision(fixRequestContext(t, "cf-sa30"), instanceID, domain.ProvisionDetails{
ServiceID: serviceID,
PlanID: broker.PreviewPlanID,
RawParameters: json.RawMessage(fmt.Sprintf(`{"name": "%s", "region": "%s","additionalWorkerNodePools": %s }`, clusterName, "eu-central-1", additionalWorkerNodePools)),
RawContext: json.RawMessage(fmt.Sprintf(`{"globalaccount_id": "%s", "subaccount_id": "%s", "user_id": "%s"}`, "any-global-account-id", subAccountID, "[email protected]")),
}, true)
t.Logf("%+v\n", *provisionEndpoint)

// then
require.NoError(t, err)
})

t.Run("Should fail for autoScalerMin bigger than autoScalerMax", func(t *testing.T) {
// given
memoryStorage := storage.NewMemoryStorage()

queue := &automock.Queue{}
queue.On("Add", mock.AnythingOfType("string"))

factoryBuilder := &automock.PlanValidator{}
factoryBuilder.On("IsPlanSupport", broker.PreviewPlanID).Return(true)

planDefaults := func(planID string, platformProvider pkg.CloudProvider, provider *pkg.CloudProvider) (*gqlschema.ClusterConfigInput, error) {
return &gqlschema.ClusterConfigInput{}, nil
}
kcBuilder := &kcMock.KcBuilder{}
kcBuilder.On("GetServerURL", "").Return("", fmt.Errorf("error"))
// #create provisioner endpoint
provisionEndpoint := broker.NewProvision(
broker.Config{
EnablePlans: []string{"preview"},
URL: brokerURL,
OnlySingleTrialPerGA: true,
EnableKubeconfigURLLabel: true,
},
gardener.Config{Project: "test", ShootDomain: "example.com", DNSProviders: fixDNSProviders()},
memoryStorage.Operations(),
memoryStorage.Instances(),
memoryStorage.InstancesArchived(),
queue,
factoryBuilder,
broker.PlansConfig{},
planDefaults,
log,
dashboardConfig,
kcBuilder,
whitelist.Set{},
&broker.OneForAllConvergedCloudRegionsProvider{},
)

additionalWorkerNodePools := `[{"name": "name-1", "machineType": "m6i.large", "autoScalerMin": 20, "autoScalerMax": 3}, {"name": "name-2", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}]`

// when
_, err := provisionEndpoint.Provision(fixRequestContext(t, "cf-sa30"), instanceID, domain.ProvisionDetails{
ServiceID: serviceID,
PlanID: broker.PreviewPlanID,
RawParameters: json.RawMessage(fmt.Sprintf(`{"name": "%s", "region": "%s","additionalWorkerNodePools": %s }`, clusterName, "eu-central-1", additionalWorkerNodePools)),
RawContext: json.RawMessage(fmt.Sprintf(`{"globalaccount_id": "%s", "subaccount_id": "%s", "user_id": "%s"}`, "any-global-account-id", subAccountID, "[email protected]")),
}, true)
t.Logf("%+v\n", *provisionEndpoint)

// then
require.EqualError(t, err, "AutoScalerMax 3 should be larger than AutoScalerMin 20. User provided values min:20, max:3; plan defaults min:0, max:0")
})
}

func TestNetworkingValidation(t *testing.T) {
Expand Down
20 changes: 20 additions & 0 deletions internal/broker/instance_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ func (b *UpdateEndpoint) processUpdateParameters(instance *internal.Instance, de
logger.Error(fmt.Sprintf("invalid autoscaler parameters: %s", err.Error()))
return domain.UpdateServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusBadRequest, err.Error())
}

if IsPreviewPlan(details.PlanID) {
for _, workerNodePool := range params.AdditionalWorkerNodePools {
if err := workerNodePool.AutoScalerParameters.Validate(autoscalerMin, autoscalerMax); err != nil {
return domain.UpdateServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusBadRequest, err.Error())
}
}
}

err = b.operationStorage.InsertOperation(operation)
if err != nil {
return domain.UpdateServiceSpec{}, err
Expand All @@ -287,6 +296,17 @@ func (b *UpdateEndpoint) processUpdateParameters(instance *internal.Instance, de
if params.MachineType != nil && *params.MachineType != "" {
instance.Parameters.Parameters.MachineType = params.MachineType
}

if IsPreviewPlan(details.PlanID) {
// if the list is empty remove additional worker node pools
if params.AdditionalWorkerNodePools != nil {
newAdditionalWorkerNodePools := make([]pkg.AdditionalWorkerNodePool, 0, len(params.AdditionalWorkerNodePools))
newAdditionalWorkerNodePools = append(newAdditionalWorkerNodePools, params.AdditionalWorkerNodePools...)
instance.Parameters.Parameters.AdditionalWorkerNodePools = newAdditionalWorkerNodePools
updateStorage = append(updateStorage, "Additional Worker Node Pools")
}
}

if len(updateStorage) > 0 {
if err := wait.PollUntilContextTimeout(context.Background(), 500*time.Millisecond, 2*time.Second, true, func(ctx context.Context) (bool, error) {
instance, err = b.instanceStorage.Update(*instance)
Expand Down
60 changes: 60 additions & 0 deletions internal/broker/instance_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,66 @@ func TestUpdateEndpoint_UpdateParameters(t *testing.T) {
assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil))
assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction())
})

t.Run("Should pass with additional worker node pools", func(t *testing.T) {
// given
additionalWorkerNodePools := `[{"name": "name-1", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}, {"name": "name-2", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}]`

// when
_, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{
ServiceID: "",
PlanID: PreviewPlanID,
RawParameters: json.RawMessage("{\"additionalWorkerNodePools\":" + additionalWorkerNodePools + "}"),
PreviousValues: domain.PreviousValues{},
RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"),
MaintenanceInfo: nil,
}, true)

// then
require.NoError(t, err)
})

t.Run("Should pass with empty additional worker node pools", func(t *testing.T) {
// given
additionalWorkerNodePools := "[]"

// when
_, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{
ServiceID: "",
PlanID: PreviewPlanID,
RawParameters: json.RawMessage("{\"additionalWorkerNodePools\":" + additionalWorkerNodePools + "}"),
PreviousValues: domain.PreviousValues{},
RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"),
MaintenanceInfo: nil,
}, true)

// then
require.NoError(t, err)
})

t.Run("Should fail for autoScalerMin bigger than autoScalerMax", func(t *testing.T) {
// given
additionalWorkerNodePools := `[{"name": "name-1", "machineType": "m6i.large", "autoScalerMin": 20, "autoScalerMax": 3}, {"name": "name-2", "machineType": "m6i.large", "autoScalerMin": 3, "autoScalerMax": 20}]`
errMsg := fmt.Errorf("AutoScalerMax 3 should be larger than AutoScalerMin 20. User provided values min:20, max:3; plan defaults min:0, max:0")
expectedErr := apiresponses.NewFailureResponse(errMsg, http.StatusBadRequest, errMsg.Error())

// when
_, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{
ServiceID: "",
PlanID: PreviewPlanID,
RawParameters: json.RawMessage("{\"additionalWorkerNodePools\":" + additionalWorkerNodePools + "}"),
PreviousValues: domain.PreviousValues{},
RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"),
MaintenanceInfo: nil,
}, true)

// then
require.Error(t, err)
assert.IsType(t, &apiresponses.FailureResponse{}, err)
apierr := err.(*apiresponses.FailureResponse)
assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil))
assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction())
})
}

func TestUpdateEndpoint_UpdateWithEnabledDashboard(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions internal/broker/plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ func SapConvergedCloudSchema(machineTypesDisplay, regionsDisplay map[string]stri
func PreviewSchema(machineTypesDisplay, regionsDisplay map[string]string, machineTypes []string, additionalParams, update bool, euAccessRestricted bool) *map[string]interface{} {
properties := NewProvisioningProperties(machineTypesDisplay, regionsDisplay, machineTypes, AWSRegions(euAccessRestricted), update)
properties.Networking = NewNetworkingSchema()
properties.AdditionalWorkerNodePools = NewAdditionalWorkerNodePoolsSchema(machineTypesDisplay, machineTypes)
return createSchemaWithProperties(properties, additionalParams, update, requiredSchemaProperties(), false, false)
}

Expand Down
Loading

0 comments on commit 02c7833

Please sign in to comment.