diff --git a/api/v1alpha1/scheduledscaler_types.go b/api/v1alpha1/scheduledscaler_types.go index cd86d5d..dc8c3a4 100644 --- a/api/v1alpha1/scheduledscaler_types.go +++ b/api/v1alpha1/scheduledscaler_types.go @@ -29,22 +29,34 @@ const ( PhaseDone = "DONE" ) +type Schedule struct { + // Start time for scheduling + Start string `json:"start,omitempty"` + // End time for scheduling + End string `json:"end,omitempty"` +} + // ScheduledScalerSpec defines the desired state of ScheduledScaler type ScheduledScalerSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of ScheduledScaler. Edit scheduledscaler_types.go to remove/update - Schedule string `json:"schedule,omitempty"` - DeploymentName string `json:"deploymentName,omitempty"` - ReplicaCount int32 `json:"replicaCount,omitempty"` + // Schedule defines the time between scaling up and down + Schedule Schedule `json:"schedule,omitempty"` + // DeploymentName defines target of deployment + DeploymentName string `json:"deploymentName,omitempty"` + // ReplicaCount defines how many replicas deployment will scale into + ReplicaCount int32 `json:"replicaCount,omitempty"` } // ScheduledScalerStatus defines the observed state of ScheduledScaler type ScheduledScalerStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file + + // StoredReplicaCount store information original replicas StoredReplicaCount int32 `json:"storedReplicaCount,omitempty"` + // Phase store information about phase of this resource Phase string `json:"phase,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9dc1126..de4f2a2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,21 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Schedule) DeepCopyInto(out *Schedule) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Schedule. +func (in *Schedule) DeepCopy() *Schedule { + if in == nil { + return nil + } + out := new(Schedule) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScheduledScaler) DeepCopyInto(out *ScheduledScaler) { *out = *in @@ -86,6 +101,7 @@ func (in *ScheduledScalerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScheduledScalerSpec) DeepCopyInto(out *ScheduledScalerSpec) { *out = *in + out.Schedule = in.Schedule } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduledScalerSpec. diff --git a/config/crd/bases/scaler.andikahmadr.io_scheduledscalers.yaml b/config/crd/bases/scaler.andikahmadr.io_scheduledscalers.yaml index bb46ee0..8ef0a91 100644 --- a/config/crd/bases/scaler.andikahmadr.io_scheduledscalers.yaml +++ b/config/crd/bases/scaler.andikahmadr.io_scheduledscalers.yaml @@ -50,24 +50,32 @@ spec: description: ScheduledScalerSpec defines the desired state of ScheduledScaler properties: deploymentName: + description: DeploymentName defines target of deployment type: string replicaCount: + description: ReplicaCount defines how many replicas deployment will + scale into format: int32 type: integer schedule: - description: Foo is an example field of ScheduledScaler. Edit scheduledscaler_types.go - to remove/update - type: string + description: Schedule defines the time between scaling up and down + properties: + end: + description: End time for scheduling + type: string + start: + description: Start time for scheduling + type: string + type: object type: object status: description: ScheduledScalerStatus defines the observed state of ScheduledScaler properties: phase: + description: Phase store information about phase of this resource type: string storedReplicaCount: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' + description: StoredReplicaCount store information original replicas format: int32 type: integer type: object diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5e793dd..8b899ae 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -12,5 +12,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: controller - newTag: latest + newName: kmdr7/scheduled-scaler-operator + newTag: "1.0" diff --git a/config/samples/deployment2.yaml b/config/samples/deployment2.yaml index b878cda..ae1ed01 100644 --- a/config/samples/deployment2.yaml +++ b/config/samples/deployment2.yaml @@ -7,7 +7,7 @@ spec: selector: matchLabels: app: web-2 - replicas: 1 + replicas: 2 template: metadata: labels: diff --git a/config/samples/scaler_v1alpha1_scheduledscaler.yaml b/config/samples/scaler_v1alpha1_scheduledscaler.yaml index 5289e64..61600b9 100644 --- a/config/samples/scaler_v1alpha1_scheduledscaler.yaml +++ b/config/samples/scaler_v1alpha1_scheduledscaler.yaml @@ -4,6 +4,8 @@ metadata: name: scheduledscaler-sample namespace: web spec: - schedule: "2021-09-09T14:54:00Z,2021-09-09T14:55:00Z" + schedule: + start: "2021-09-11T14:54:00Z" + end: "2021-09-11T14:55:00Z" deploymentName: "web-2" replicaCount: 5 diff --git a/controllers/scheduledscaler_controller.go b/controllers/scheduledscaler_controller.go index 8cd093a..21eaa8a 100644 --- a/controllers/scheduledscaler_controller.go +++ b/controllers/scheduledscaler_controller.go @@ -21,11 +21,11 @@ import ( "fmt" "github.com/go-logr/logr" scalerv1alpha1 "github.com/kmdrn7/scheduled-scaler-operator/api/v1alpha1" + timeUtils "github.com/kmdrn7/scheduled-scaler-operator/pkg/time" appsv1 "k8s.io/api/apps/v1" "os" "sigs.k8s.io/controller-runtime/pkg/client/config" "strconv" - "strings" "time" "k8s.io/apimachinery/pkg/api/errors" @@ -85,24 +85,16 @@ func (r *ScheduledScalerReconciler) Reconcile(ctx context.Context, req ctrl.Requ instance.Status.StoredReplicaCount = -1 } - timeLayout := "2006-01-02T15:04:05Z" - timeZone, err := time.LoadLocation("Asia/Jakarta") - if err != nil { - fmt.Println("Error set timezone") - } - - now := time.Now().UTC().In(timeZone) - schedule := instance.Spec.Schedule - scheduleSplit := strings.Split(schedule, ",") - scheduleStart := scheduleSplit[0] - scheduleEnd := scheduleSplit[1] + now := timeUtils.Now() + scheduleStart := instance.Spec.Schedule.Start + scheduleEnd := instance.Spec.Schedule.End - timeStart, err := time.ParseInLocation(timeLayout, scheduleStart, timeZone) + timeStart, err := timeUtils.Parse(scheduleStart) if err != nil { fmt.Println("Error parsing start time") } - timeEnd, err := time.ParseInLocation(timeLayout, scheduleEnd, timeZone) + timeEnd, err := timeUtils.Parse(scheduleEnd) if err != nil { fmt.Println("Error parsing end time") } @@ -111,7 +103,7 @@ func (r *ScheduledScalerReconciler) Reconcile(ctx context.Context, req ctrl.Requ switch instance.Status.Phase { case scalerv1alpha1.PhasePending: log.Info(req.NamespacedName.String() + " still in pending phase") - log.Info(req.NamespacedName.String()+" detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) + log.Info(req.NamespacedName.String() + " detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) if now.Before(timeStart) { reconcileAfter := timeStart.Sub(now) @@ -124,9 +116,9 @@ func (r *ScheduledScalerReconciler) Reconcile(ctx context.Context, req ctrl.Requ case scalerv1alpha1.PhaseRunning: log.Info(req.NamespacedName.String() + " is in running phase") - log.Info(req.NamespacedName.String()+" detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) + log.Info(req.NamespacedName.String() + " detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) - //get deployment + // get deployment deployment := &appsv1.Deployment{} err := cl.Get(ctx, client.ObjectKey{ Namespace: instance.Namespace, @@ -155,7 +147,7 @@ func (r *ScheduledScalerReconciler) Reconcile(ctx context.Context, req ctrl.Requ deployment.Spec.Replicas = &instance.Spec.ReplicaCount err := cl.Update(ctx, deployment) if err != nil { - log.Error(err, "Error updating deployment "+deployment.Name) + log.Error(err, "Error updating deployment " + deployment.Name) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } log.Info("Successfully scaling " + deployment.Name + " with " + strconv.Itoa(int(instance.Spec.ReplicaCount)) + " replicas") @@ -171,9 +163,9 @@ func (r *ScheduledScalerReconciler) Reconcile(ctx context.Context, req ctrl.Requ case scalerv1alpha1.PhaseDone: log.Info(req.NamespacedName.String() + " is in done phase") - log.Info(req.NamespacedName.String()+" detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) + log.Info(req.NamespacedName.String() + " detail", "now", now.String(), "time start", timeStart.String(), "stored replica count", strconv.Itoa(int(instance.Status.StoredReplicaCount))) - //get deployment + // get deployment deployment := &appsv1.Deployment{} err := cl.Get(ctx, client.ObjectKey{ Namespace: instance.Namespace, diff --git a/pkg/time/time.go b/pkg/time/time.go new file mode 100644 index 0000000..e83614d --- /dev/null +++ b/pkg/time/time.go @@ -0,0 +1,30 @@ +package time + +import ( + "fmt" + "time" +) + +var ( + timeLayout = "2006-01-02T15:04:05Z" + timeZone, err = time.LoadLocation("Asia/Jakarta") +) + +func init() { + if err != nil { + fmt.Println("Error configuring timezone") + panic(err) + } +} + +func Now() time.Time { + return time.Now().UTC().In(timeZone) +} + +func Parse(timeString string) (time.Time, error) { + parsedTime, err := time.ParseInLocation(timeLayout, timeString, timeZone) + if err != nil { + return time.Time{}, err + } + return parsedTime, nil +}