Skip to content

Commit

Permalink
feat: suspend only cronjobs
Browse files Browse the repository at this point in the history
  • Loading branch information
davidebianchi committed Aug 20, 2022
1 parent 77b514a commit 0c0ca3c
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 26 deletions.
11 changes: 11 additions & 0 deletions api/v1alpha1/sleepinfo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ type SleepInfoSpec struct {
// +optional
//+operator-sdk:csv:customresourcedefinitions:type=spec
SuspendCronjobs bool `json:"suspendCronJobs,omitempty"`
// If SuspendDeployments is set to false, on sleep the deployment of the namespace will not be suspended. By default Deployment will be suspended.
// +optional
//+operator-sdk:csv:customresourcedefinitions:type=spec
SuspendDeployments *bool `json:"suspendDeployments,omitempty"`
}

// SleepInfoStatus defines the observed state of SleepInfo
Expand Down Expand Up @@ -125,6 +129,13 @@ func (s SleepInfo) IsCronjobsToSuspend() bool {
return s.Spec.SuspendCronjobs
}

func (s SleepInfo) IsDeploymentsToSuspend() bool {
if s.Spec.SuspendDeployments == nil {
return true
}
return s.Spec.SuspendCronjobs
}

//+kubebuilder:object:root=true

// SleepInfoList contains a list of SleepInfo
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/crd/bases/kube-green.com_sleepinfos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ spec:
description: If SuspendCronjobs is set to true, on sleep the cronjobs
of the namespace will be suspended.
type: boolean
suspendDeployments:
description: If SuspendDeployments is set to false, on sleep the deployment
of the namespace will not be suspended. By default Deployment will
be suspended.
type: boolean
timeZone:
description: Time zone to set the schedule, in IANA time zone identifier.
It is not required, default to UTC. For example, for the Italy time
Expand Down
9 changes: 9 additions & 0 deletions controllers/sleepinfo/deployments/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ type deployments struct {
resource.ResourceClient
data []appsv1.Deployment
OriginalReplicas map[string]int32
areToSuspend bool
}

func NewResource(ctx context.Context, res resource.ResourceClient, namespace string, originalReplicas map[string]int32) (deployments, error) {
d := deployments{
ResourceClient: res,
OriginalReplicas: originalReplicas,
data: []appsv1.Deployment{},
areToSuspend: res.SleepInfo.IsDeploymentsToSuspend(),
}
if !d.areToSuspend {
return d, nil
}
if err := d.fetch(ctx, namespace); err != nil {
return deployments{}, err
Expand Down Expand Up @@ -128,6 +134,9 @@ type OriginalReplicas struct {
}

func (d deployments) GetOriginalInfoToSave() ([]byte, error) {
if !d.areToSuspend {
return nil, nil
}
originalDeploymentsReplicas := []OriginalReplicas{}
for _, deployment := range d.data {
originalReplicas := *deployment.Spec.Replicas
Expand Down
17 changes: 17 additions & 0 deletions controllers/sleepinfo/deployments/deployments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ func TestNewResource(t *testing.T) {
Build(),
expected: []appsv1.Deployment{},
},
{
name: "disabled cronjob suspend",
client: fake.
NewClientBuilder().
WithRuntimeObjects([]runtime.Object{&deployment1, &deployment2, &deploymentOtherNamespace}...).
Build(),
sleepInfo: &v1alpha1.SleepInfo{
Spec: v1alpha1.SleepInfoSpec{
SuspendDeployments: getPtr(true),
},
},
expected: []appsv1.Deployment{},
},
{
name: "with deployment to exclude",
client: fake.
Expand Down Expand Up @@ -440,3 +453,7 @@ func TestDeploymentOriginalReplicas(t *testing.T) {
require.Nil(t, info)
})
}

func getPtr[T any](item T) *T {
return &item
}
7 changes: 6 additions & 1 deletion controllers/sleepinfo/sleepinfo_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ func (r *SleepInfoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}
}

log.WithValues("requeueAfter", requeueAfter).Info("deployment not present in namespace")
logMsg := "deployments and cronjobs not present in namespace"
if !sleepInfo.IsCronjobsToSuspend() && !sleepInfo.IsDeploymentsToSuspend() {
logMsg = "deployments and cronjobs are not to suspend"
}
log.WithValues("requeueAfter", requeueAfter).Info(logMsg)

return ctrl.Result{
RequeueAfter: requeueAfter,
}, nil
Expand Down
112 changes: 94 additions & 18 deletions controllers/sleepinfo/sleepinfo_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ var _ = Describe("SleepInfo Controller", func() {
}))
})

By("without deployments, secret is not written", func() {
By("without deployments, in secret is written only the last schedule", func() {
secret, err := sleepInfoReconciler.getSecret(ctx, getSecretName(sleepInfoName), namespace)
Expect(err).To(BeNil())
Expect(secret).NotTo(BeNil())
Expand Down Expand Up @@ -561,11 +561,37 @@ var _ = Describe("SleepInfo Controller", func() {
req: req,
namespace: namespace,
sleepInfoName: sleepInfoName,
sleepInfo: originalResources.sleepInfo,

originalDeployments: originalResources.deploymentList,

originalCronJobs: originalResources.cronjobList,
}

assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T20:05:59.000Z").withRequeue(14*60 + 1))
assertCorrectWakeUpOperation(assertContextInfo.withSchedule("2021-03-23T20:19:50.100Z").withRequeue(45*60 + 9.9))
assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T21:05:00.000Z").withRequeue(15 * 60))
})

It("reconcile - with only cron jobs", func() {
namespace := "only-cronjobs-suspend"
req, originalResources := setupNamespaceWithResources(ctx, sleepInfoName, namespace, sleepInfoReconciler, mockNow, setupOptions{
suspendCronjobs: true,
insertCronjobs: true,
suspendDeployments: getPtr(false),
})

assertContextInfo := AssertOperation{
testLogger: testLogger,
ctx: ctx,
req: req,
namespace: namespace,
sleepInfoName: sleepInfoName,
sleepInfo: originalResources.sleepInfo,

originalDeployments: originalResources.deploymentList,

originalCronJobs: originalResources.cronjobList,
suspendCronjobs: true,
}

assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T20:05:59.000Z").withRequeue(14*60 + 1))
Expand All @@ -585,10 +611,10 @@ var _ = Describe("SleepInfo Controller", func() {
req: req,
namespace: namespace,
sleepInfoName: sleepInfoName,
sleepInfo: originalResources.sleepInfo,

originalDeployments: originalResources.deploymentList,

suspendCronjobs: true,
originalCronJobs: originalResources.cronjobList,
}

Expand All @@ -597,7 +623,7 @@ var _ = Describe("SleepInfo Controller", func() {
assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T21:05:00.000Z").withRequeue(15 * 60))
})

It("reconcile - with deployments and cron jobs not to suspend", func() {
It("reconcile - with deployments and only cron jobs not to suspend", func() {
namespace := "deployments-sleep-cronjobs-not-suspend"
req, originalResources := setupNamespaceWithResources(ctx, sleepInfoName, namespace, sleepInfoReconciler, mockNow, setupOptions{
insertCronjobs: true,
Expand All @@ -620,6 +646,31 @@ var _ = Describe("SleepInfo Controller", func() {
assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T21:05:00.000Z").withRequeue(15 * 60))
})

It("reconcile - with both deployments and cron jobs not to suspend", func() {
namespace := "deployments-and-cronjobs-not-suspend"
req, originalResources := setupNamespaceWithResources(ctx, sleepInfoName, namespace, sleepInfoReconciler, mockNow, setupOptions{
insertCronjobs: true,
suspendCronjobs: false,
suspendDeployments: getPtr(false),
})

assertContextInfo := AssertOperation{
testLogger: testLogger,
ctx: ctx,
req: req,
namespace: namespace,
sleepInfoName: sleepInfoName,
sleepInfo: originalResources.sleepInfo,

originalDeployments: originalResources.deploymentList,

originalCronJobs: originalResources.cronjobList,
}

assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T20:05:59.000Z").withRequeue(59*60 + 1))
assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T21:05:00.000Z").withRequeue(60 * 60))
})

It("reconcile - with deployments and cron job to exclude", func() {
namespace := "multiple-cronjob-exclude"
req, originalResources := setupNamespaceWithResources(ctx, sleepInfoName, namespace, sleepInfoReconciler, mockNow, setupOptions{
Expand Down Expand Up @@ -647,8 +698,8 @@ var _ = Describe("SleepInfo Controller", func() {
sleepInfoName: sleepInfoName,
originalDeployments: originalResources.deploymentList,
excludedDeployment: []string{"service-1"},
sleepInfo: originalResources.sleepInfo,

suspendCronjobs: true,
originalCronJobs: originalResources.cronjobList,
excludedCronJob: []string{"cronjob-2"},
}
Expand All @@ -671,11 +722,11 @@ var _ = Describe("SleepInfo Controller", func() {
req: req,
namespace: namespace,
sleepInfoName: sleepInfoName,
sleepInfo: originalResources.sleepInfo,

originalDeployments: originalResources.deploymentList,

originalCronJobs: originalResources.cronjobList,
suspendCronjobs: true,
}
assertCorrectSleepOperation(assertContextInfo.withSchedule("2021-03-23T20:05:59.000Z").withRequeue(14*60 + 1))

Expand Down Expand Up @@ -746,14 +797,14 @@ type AssertOperation struct {
namespace string
sleepInfoName string
scheduleTime string
sleepInfo kubegreenv1alpha1.SleepInfo
// optional - default is equal to scheduleTime
expectedScheduleTime string
expectedNextRequeue time.Duration

originalDeployments []appsv1.Deployment
excludedDeployment []string

suspendCronjobs bool
originalCronJobs []unstructured.Unstructured
excludedCronJob []string
}
Expand Down Expand Up @@ -790,23 +841,30 @@ func assertCorrectSleepOperation(assert AssertOperation) {

By("replicas are set to 0 to all deployments set to sleep", func() {
deployments := listDeployments(assert.ctx, assert.namespace)
if len(assert.excludedDeployment) == 0 {
assertAllReplicasSetToZero(deployments, assert.originalDeployments)
if assert.sleepInfo.IsDeploymentsToSuspend() {
if len(assert.excludedDeployment) == 0 {
assertAllReplicasSetToZero(deployments, assert.originalDeployments)
} else {
for _, deployment := range deployments {
if contains(assert.excludedDeployment, deployment.Name) {
originalDeployment := findDeployByName(assert.originalDeployments, deployment.GetName())
Expect(*deployment.Spec.Replicas).To(Equal(*originalDeployment.Spec.Replicas))
continue
}
Expect(*deployment.Spec.Replicas).To(Equal(int32(0)))
}
}
} else {
for _, deployment := range deployments {
if contains(assert.excludedDeployment, deployment.Name) {
originalDeployment := findDeployByName(assert.originalDeployments, deployment.Name)
Expect(*deployment.Spec.Replicas).To(Equal(*originalDeployment.Spec.Replicas))
continue
}
Expect(*deployment.Spec.Replicas).To(Equal(int32(0)))
originalDeployment := findDeployByName(assert.originalDeployments, deployment.GetName())
Expect(*deployment.Spec.Replicas).To(Equal(*originalDeployment.Spec.Replicas))
}
}
})

By("cron jobs are correctly suspended", func() {
cronJobs := listCronJobs(assert.ctx, assert.namespace)
if assert.suspendCronjobs {
if assert.sleepInfo.IsCronjobsToSuspend() {
if len(assert.excludedCronJob) == 0 {
assertAllCronJobsSuspended(cronJobs, assert.originalCronJobs)
} else {
Expand Down Expand Up @@ -840,6 +898,13 @@ func assertCorrectSleepOperation(assert AssertOperation) {
Expect(err).NotTo(HaveOccurred())
secretData := secret.Data

if !assert.sleepInfo.IsCronjobsToSuspend() && !assert.sleepInfo.IsDeploymentsToSuspend() {
Expect(secretData).To(Equal(map[string][]byte{
lastScheduleKey: []byte(getTime(assert.expectedScheduleTime).Truncate(time.Second).Format(time.RFC3339)),
}))
return
}

type ExpectedReplicas struct {
Name string `json:"name"`
Replicas int32 `json:"replicas"`
Expand All @@ -864,7 +929,7 @@ func assertCorrectSleepOperation(assert AssertOperation) {
replicasBeforeSleepKey: expectedReplicas,
}

if assert.suspendCronjobs {
if assert.sleepInfo.IsCronjobsToSuspend() {
originalStatus := []cronjobs.OriginalCronJobStatus{}
for _, cronJob := range assert.originalCronJobs {
if isCronJobSuspended(cronJob) {
Expand All @@ -890,9 +955,16 @@ func assertCorrectSleepOperation(assert AssertOperation) {
By("sleepinfo status updated correctly", func() {
sleepInfo, err := sleepInfoReconciler.getSleepInfo(assert.ctx, assert.req)
Expect(err).NotTo(HaveOccurred())

operationType := sleepOperation

if !sleepInfo.IsCronjobsToSuspend() && !sleepInfo.IsDeploymentsToSuspend() {
operationType = ""
}

Expect(sleepInfo.Status).To(Equal(kubegreenv1alpha1.SleepInfoStatus{
LastScheduleTime: metav1.NewTime(getTime(assert.expectedScheduleTime).Local()),
OperationType: sleepOperation,
OperationType: operationType,
}))
})

Expand Down Expand Up @@ -960,3 +1032,7 @@ func assertCorrectWakeUpOperation(assert AssertOperation) {
}))
})
}

func getPtr[T any](item T) *T {
return &item
}

0 comments on commit 0c0ca3c

Please sign in to comment.