From 06b85c5965f296b05999f9934cb7891df0aae353 Mon Sep 17 00:00:00 2001 From: cristi Date: Thu, 28 Nov 2024 11:22:38 +0200 Subject: [PATCH] feat: sss add zone deployment strategies for AUTO, zone_1 zone_2 zone_3 --- apis/compute/v1alpha1/serverset_types.go | 4 ++ .../v1alpha1/statefulserverset_types.go | 2 +- .../compute/v1alpha1/zz_generated.deepcopy.go | 1 + .../datavolume_controller.go | 14 ++++--- .../statefulserverset/serverset_controller.go | 1 + .../serverset/bootvolume_controller.go | 18 ++++---- .../controller/serverset/server_controller.go | 24 +++++------ .../serverset/server_controller_test.go | 25 +++++++---- .../serverset/zone_deployment_strategy.go | 42 +++++++++++++++++++ ...e.ionoscloud.crossplane.io_serversets.yaml | 15 +++++++ ...loud.crossplane.io_statefulserversets.yaml | 4 ++ 11 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 internal/controller/serverset/zone_deployment_strategy.go diff --git a/apis/compute/v1alpha1/serverset_types.go b/apis/compute/v1alpha1/serverset_types.go index 39f74c8a..936bfb13 100644 --- a/apis/compute/v1alpha1/serverset_types.go +++ b/apis/compute/v1alpha1/serverset_types.go @@ -43,6 +43,8 @@ type ServerSetParameters struct { // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=1 Replicas int `json:"replicas"` + // +kubebuilder:validation:Optional + DeploymentStrategy DeploymentStrategy `json:"deploymentStrategy"` // DatacenterConfig contains information about the datacenter resource // on which the server will be created. // @@ -263,6 +265,8 @@ const ( CreateBeforeDestroyBootVolume = "createBeforeDestroyBootVolume" ) +const () + // +kubebuilder:object:root=true // ServerSet represents a stateful set of servers in the Ionos Cloud. diff --git a/apis/compute/v1alpha1/statefulserverset_types.go b/apis/compute/v1alpha1/statefulserverset_types.go index 3dd9cedf..b772e52e 100644 --- a/apis/compute/v1alpha1/statefulserverset_types.go +++ b/apis/compute/v1alpha1/statefulserverset_types.go @@ -27,7 +27,7 @@ import ( // DeploymentStrategy describes what strategy should be used to deploy the servers. type DeploymentStrategy struct { - // +kubebuilder:validation:Enum=ZONES + // +kubebuilder:validation:Enum=ZONES;AUTO;ZONE_1;ZONE_2;ZONE_3 Type string `json:"type"` } diff --git a/apis/compute/v1alpha1/zz_generated.deepcopy.go b/apis/compute/v1alpha1/zz_generated.deepcopy.go index 567f0935..7b1b2135 100644 --- a/apis/compute/v1alpha1/zz_generated.deepcopy.go +++ b/apis/compute/v1alpha1/zz_generated.deepcopy.go @@ -1949,6 +1949,7 @@ func (in *ServerSetObservation) DeepCopy() *ServerSetObservation { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerSetParameters) DeepCopyInto(out *ServerSetParameters) { *out = *in + out.DeploymentStrategy = in.DeploymentStrategy in.DatacenterCfg.DeepCopyInto(&out.DatacenterCfg) in.Template.DeepCopyInto(&out.Template) in.BootVolumeTemplate.DeepCopyInto(&out.BootVolumeTemplate) diff --git a/internal/controller/compute/statefulserverset/datavolume_controller.go b/internal/controller/compute/statefulserverset/datavolume_controller.go index 710565a4..3f9ae2b6 100644 --- a/internal/controller/compute/statefulserverset/datavolume_controller.go +++ b/internal/controller/compute/statefulserverset/datavolume_controller.go @@ -149,13 +149,17 @@ func fromSSSetToVolume(cr *v1alpha1.StatefulServerSet, name string, replicaIndex DeletionPolicy: cr.GetDeletionPolicy(), }, ForProvider: v1alpha1.VolumeParameters{ - DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, - Name: generateProviderNameFromIndex(cr.Spec.ForProvider.Volumes[volumeIndex].Metadata.Name, volumeIndex), - AvailabilityZone: serverset.GetZoneFromIndex(replicaIndex), - Size: cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Size, - Type: cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Type, + DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, + Name: generateProviderNameFromIndex(cr.Spec.ForProvider.Volumes[volumeIndex].Metadata.Name, volumeIndex), + Size: cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Size, + Type: cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Type, }, }} + options := serverset.ZoneDeploymentOptions{ + Zone: cr.Spec.ForProvider.DeploymentStrategy.Type, + Index: replicaIndex, + } + vol.Spec.ForProvider.AvailabilityZone = serverset.NewZoneDeploymentByType(cr.Spec.ForProvider.DeploymentStrategy.Type).GetZone(options) if cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Image != "" { vol.Spec.ForProvider.Image = cr.Spec.ForProvider.Volumes[volumeIndex].Spec.Image // todo - this will not work without a password diff --git a/internal/controller/compute/statefulserverset/serverset_controller.go b/internal/controller/compute/statefulserverset/serverset_controller.go index 2c043f38..74d312d7 100644 --- a/internal/controller/compute/statefulserverset/serverset_controller.go +++ b/internal/controller/compute/statefulserverset/serverset_controller.go @@ -155,6 +155,7 @@ func extractSSetFromSSSet(sSSet *v1alpha1.StatefulServerSet) *v1alpha1.ServerSet }, ForProvider: v1alpha1.ServerSetParameters{ Replicas: sSSet.Spec.ForProvider.Replicas, + DeploymentStrategy: sSSet.Spec.ForProvider.DeploymentStrategy, DatacenterCfg: sSSet.Spec.ForProvider.DatacenterCfg, Template: sSSet.Spec.ForProvider.Template, BootVolumeTemplate: sSSet.Spec.ForProvider.BootVolumeTemplate, diff --git a/internal/controller/serverset/bootvolume_controller.go b/internal/controller/serverset/bootvolume_controller.go index 3414425b..8e04659a 100644 --- a/internal/controller/serverset/bootvolume_controller.go +++ b/internal/controller/serverset/bootvolume_controller.go @@ -216,15 +216,19 @@ func fromServerSetToVolume(cr *v1alpha1.ServerSet, name string, replicaIndex, ve DeletionPolicy: cr.GetDeletionPolicy(), }, ForProvider: v1alpha1.VolumeParameters{ - DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, - Name: name, - AvailabilityZone: GetZoneFromIndex(replicaIndex), - Size: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Size, - Type: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Type, - Image: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Image, - UserData: cr.Spec.ForProvider.BootVolumeTemplate.Spec.UserData, + DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, + Name: name, + Size: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Size, + Type: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Type, + Image: cr.Spec.ForProvider.BootVolumeTemplate.Spec.Image, + UserData: cr.Spec.ForProvider.BootVolumeTemplate.Spec.UserData, }, }} + options := ZoneDeploymentOptions{ + Zone: cr.Spec.ForProvider.DeploymentStrategy.Type, + Index: replicaIndex, + } + vol.Spec.ForProvider.AvailabilityZone = NewZoneDeploymentByType(cr.Spec.ForProvider.DeploymentStrategy.Type).GetZone(options) if cr.Spec.ForProvider.BootVolumeTemplate.Spec.ImagePassword != "" { vol.Spec.ForProvider.ImagePassword = cr.Spec.ForProvider.BootVolumeTemplate.Spec.ImagePassword } diff --git a/internal/controller/serverset/server_controller.go b/internal/controller/serverset/server_controller.go index 4f36b706..4f00774b 100644 --- a/internal/controller/serverset/server_controller.go +++ b/internal/controller/serverset/server_controller.go @@ -118,7 +118,7 @@ func (k *kubeServerController) isServerDeleted(ctx context.Context, name, namesp // fromServerSetToServer is a conversion function that converts a ServerSet resource to a Server resource // attaches a bootvolume to the server based on replicaIndex func fromServerSetToServer(cr *v1alpha1.ServerSet, replicaIndex, version int) v1alpha1.Server { - return v1alpha1.Server{ + srv := v1alpha1.Server{ ObjectMeta: metav1.ObjectMeta{ Name: getNameFrom(cr.Spec.ForProvider.Template.Metadata.Name, replicaIndex, version), Namespace: cr.Namespace, @@ -135,12 +135,11 @@ func fromServerSetToServer(cr *v1alpha1.ServerSet, replicaIndex, version int) v1 DeletionPolicy: cr.GetDeletionPolicy(), }, ForProvider: v1alpha1.ServerParameters{ - DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, - Name: getNameFrom(cr.Spec.ForProvider.Template.Metadata.Name, replicaIndex, version), - Cores: cr.Spec.ForProvider.Template.Spec.Cores, - RAM: cr.Spec.ForProvider.Template.Spec.RAM, - AvailabilityZone: GetZoneFromIndex(replicaIndex), - CPUFamily: cr.Spec.ForProvider.Template.Spec.CPUFamily, + DatacenterCfg: cr.Spec.ForProvider.DatacenterCfg, + Name: getNameFrom(cr.Spec.ForProvider.Template.Metadata.Name, replicaIndex, version), + Cores: cr.Spec.ForProvider.Template.Spec.Cores, + RAM: cr.Spec.ForProvider.Template.Spec.RAM, + CPUFamily: cr.Spec.ForProvider.Template.Spec.CPUFamily, // todo revert if we go back to attaching volume on server creation // VolumeCfg: v1alpha1.VolumeConfig{ // VolumeIDRef: &xpv1.Reference{ @@ -149,11 +148,12 @@ func fromServerSetToServer(cr *v1alpha1.ServerSet, replicaIndex, version int) v1 // }, }, }} -} - -// GetZoneFromIndex returns ZONE_2 for odd and ZONE_1 for even index -func GetZoneFromIndex(index int) string { - return fmt.Sprintf("ZONE_%d", index%2+1) + options := ZoneDeploymentOptions{ + Zone: cr.Spec.ForProvider.DeploymentStrategy.Type, + Index: replicaIndex, + } + srv.Spec.ForProvider.AvailabilityZone = NewZoneDeploymentByType(cr.Spec.ForProvider.DeploymentStrategy.Type).GetZone(options) + return srv } func (k *kubeServerController) Ensure(ctx context.Context, cr *v1alpha1.ServerSet, replicaIndex, version, volumeVersion int) error { diff --git a/internal/controller/serverset/server_controller_test.go b/internal/controller/serverset/server_controller_test.go index f6fbf892..b6d52bc2 100644 --- a/internal/controller/serverset/server_controller_test.go +++ b/internal/controller/serverset/server_controller_test.go @@ -4,7 +4,7 @@ import "testing" func TestGetZoneFromIndex(t *testing.T) { type args struct { - index int + deplOptions ZoneDeploymentOptions } tests := []struct { name string @@ -14,42 +14,53 @@ func TestGetZoneFromIndex(t *testing.T) { { name: "index1ExpectedZone_1", args: args{ - index: 0, + deplOptions: ZoneDeploymentOptions{ + Index: 0, + }, }, want: "ZONE_1", }, { name: "index1ExpectedZone_2", args: args{ - index: 1, + deplOptions: ZoneDeploymentOptions{ + Index: 1, + }, }, want: "ZONE_2", }, { name: "index2ExpectedZone_1", args: args{ - index: 2, + deplOptions: ZoneDeploymentOptions{ + Index: 2, + }, }, want: "ZONE_1", }, { name: "index10ExpectedZone_1", args: args{ - index: 10, + deplOptions: ZoneDeploymentOptions{ + Index: 10, + }, }, want: "ZONE_1", }, { name: "index111ExpectedZone_2", args: args{ - index: 111, + deplOptions: ZoneDeploymentOptions{ + Index: 111, + }, }, want: "ZONE_2", }, } + depl := NewZoneDeploymentByType("ZONES") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := GetZoneFromIndex(tt.args.index); got != tt.want { + if got := depl.GetZone(tt.args.deplOptions); got != tt.want { t.Errorf("GetZoneFromIndex() = %v, want %v", got, tt.want) } }) diff --git a/internal/controller/serverset/zone_deployment_strategy.go b/internal/controller/serverset/zone_deployment_strategy.go new file mode 100644 index 00000000..4799c19d --- /dev/null +++ b/internal/controller/serverset/zone_deployment_strategy.go @@ -0,0 +1,42 @@ +package serverset + +import "fmt" + +// ZoneDeploymentOptions is used to pass the zone and index to the ZoneStrategy +type ZoneDeploymentOptions struct { + // Zone needed for whateverIsSet + Zone string + // Index is needed for eachServerInAZone + Index int +} + +// ZoneStrategy is an interface that returns the zone for a server based on the ZoneDeploymentOptions +type ZoneStrategy interface { + GetZone(ZoneDeploymentOptions) string +} + +// eachServerInAZone is a ZoneStrategy that returns ZONE_2 for odd and ZONE_1 for even index +type eachServerInAZone struct { +} + +// GetZone returns ZONE_2 for odd and ZONE_1 for even index +func (e eachServerInAZone) GetZone(so ZoneDeploymentOptions) string { + return fmt.Sprintf("ZONE_%d", so.Index%2+1) +} + +// whateverIsSet is a ZoneStrategy that returns the zone set in the ZoneDeploymentOptions +type whateverIsSet struct { +} + +// GetZone returns the zone set in the ZoneDeploymentOptions +func (w whateverIsSet) GetZone(so ZoneDeploymentOptions) string { + return so.Zone +} + +// NewZoneDeploymentByType returns a ZoneStrategy based on the zone string +func NewZoneDeploymentByType(zone string) ZoneStrategy { + if zone == "" || zone == "ZONES" { + return eachServerInAZone{} + } + return whateverIsSet{} +} diff --git a/package/crds/compute.ionoscloud.crossplane.io_serversets.yaml b/package/crds/compute.ionoscloud.crossplane.io_serversets.yaml index efe03297..bbfc89f9 100644 --- a/package/crds/compute.ionoscloud.crossplane.io_serversets.yaml +++ b/package/crds/compute.ionoscloud.crossplane.io_serversets.yaml @@ -374,6 +374,21 @@ spec: type: object type: object type: object + deploymentStrategy: + description: DeploymentStrategy describes what strategy should + be used to deploy the servers. + properties: + type: + enum: + - ZONES + - AUTO + - ZONE_1 + - ZONE_2 + - ZONE_3 + type: string + required: + - type + type: object identityConfigMap: description: "IdentityConfigMap is the configMap from which the identity of the ACTIVE server in the ServerSet is read. The diff --git a/package/crds/compute.ionoscloud.crossplane.io_statefulserversets.yaml b/package/crds/compute.ionoscloud.crossplane.io_statefulserversets.yaml index 6c09edf6..c33ad443 100644 --- a/package/crds/compute.ionoscloud.crossplane.io_statefulserversets.yaml +++ b/package/crds/compute.ionoscloud.crossplane.io_statefulserversets.yaml @@ -381,6 +381,10 @@ spec: type: enum: - ZONES + - AUTO + - ZONE_1 + - ZONE_2 + - ZONE_3 type: string required: - type