Skip to content

Commit

Permalink
Update the SleepSchedule schema to support specifying which deploymen…
Browse files Browse the repository at this point in the history
…ts are required for an ingress to come back online
  • Loading branch information
azlyth committed Jun 4, 2024
1 parent 480675b commit 58a5c30
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 32 deletions.
15 changes: 11 additions & 4 deletions dummy-app/k8s-manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ spec:
sleepTime: '10:01am'
timezone: 'America/New_York'
deployments:
- dummy-frontend
- dummy-db
- dummy-redis
- name: dummy-frontend
- name: dummy-db
- name: dummy-redis
ingresses:
- dummy-ingress
- name: dummy-ingress
requires:
- deployment:
name: dummy-frontend

---

Expand Down Expand Up @@ -50,6 +53,10 @@ spec:
labels:
app: dummy-db
spec:
initContainers:
- name: sleep
image: busybox
command: ['sh', '-c', 'sleep 10']
containers:
- name: mysql
image: mysql:latest
Expand Down
21 changes: 18 additions & 3 deletions operator/api/v1beta1/sleepschedule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,29 @@ type SleepScheduleSpec struct {
// +kubebuilder:validation:Required
Timezone string `json:"timezone"`

// The names of the deployments that will be slept/woken
// The deployments that will be slept/woken.
// SHAPE: {name: "deployment-name"}
// +kubebuilder:validation:Required
Deployments []string `json:"deployments,omitempty"`
Deployments []Deployment `json:"deployments,omitempty"`

// The names of the ingresses that will be updated to point to the snorlax proxy
// which wakes the deployments when a request is received. A copy of the originals
// are stored in a configmap.
Ingresses []string `json:"ingresses,omitempty"`
// SHAPE: {name: "ingress-name", requires: [{deployment: {name: "deployment-name"}}]}
Ingresses []Ingress `json:"ingresses,omitempty"`
}

type Deployment struct {
Name string `json:"name"`
}

type IngressRequirement struct {
Deployment Deployment `json:"deployment"`
}

type Ingress struct {
Name string `json:"name"`
Requires []IngressRequirement `json:"requires,omitempty"`
}

// SleepScheduleStatus defines the observed state of SleepSchedule
Expand Down
59 changes: 56 additions & 3 deletions operator/api/v1beta1/zz_generated.deepcopy.go

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

33 changes: 30 additions & 3 deletions operator/config/crd/bases/snorlax.moonbeam.nyc_sleepschedules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,44 @@ spec:
spec:
properties:
deployments:
description: The names of the deployments that will be slept/woken
description: |-
The deployments that will be slept/woken.
SHAPE: {name: "deployment-name"}
items:
type: string
properties:
name:
type: string
required:
- name
type: object
type: array
ingresses:
description: |-
The names of the ingresses that will be updated to point to the snorlax proxy
which wakes the deployments when a request is received. A copy of the originals
are stored in a configmap.
SHAPE: {name: "ingress-name", requires: [{deployment: {name: "deployment-name"}}]}
items:
type: string
properties:
name:
type: string
requires:
items:
properties:
deployment:
properties:
name:
type: string
required:
- name
type: object
required:
- deployment
type: object
type: array
required:
- name
type: object
type: array
sleepTime:
description: The time that the deployment will start sleeping
Expand Down
79 changes: 60 additions & 19 deletions operator/internal/controller/sleepschedule_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
)

type key string

// SleepScheduleReconciler reconciles a SleepSchedule object
type SleepScheduleReconciler struct {
client.Client
Expand Down Expand Up @@ -109,6 +111,24 @@ func (r *SleepScheduleReconciler) ProcessSleepSchedule(ctx context.Context, slee
return sleepScheduleData, nil
}

func (r *SleepScheduleReconciler) waitForRequirementsToBeReady(ctx context.Context, ing *snorlaxv1beta1.Ingress) {
sleepSchedule := ctx.Value(key("sleepSchedule")).(*snorlaxv1beta1.SleepSchedule)

var requirements []snorlaxv1beta1.IngressRequirement
if len(ing.Requires) > 0 {
requirements = ing.Requires
} else {
requirements = make([]snorlaxv1beta1.IngressRequirement, len(sleepSchedule.Spec.Deployments))
for i, deployment := range sleepSchedule.Spec.Deployments {
requirements[i] = snorlaxv1beta1.IngressRequirement{Deployment: deployment}
}
}

for _, req := range requirements {
r.waitForDeploymentToWake(ctx, sleepSchedule.Namespace, req.Deployment.Name)
}
}

func (r *SleepScheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)

Expand All @@ -119,6 +139,9 @@ func (r *SleepScheduleReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Put the SleepSchedule in the context
ctx = context.WithValue(ctx, key("sleepSchedule"), sleepSchedule)

objectName := fmt.Sprintf("snorlax-%s", sleepSchedule.Name)

// Load sleep schedule data
Expand Down Expand Up @@ -201,9 +224,11 @@ func (r *SleepScheduleReconciler) Reconcile(ctx context.Context, req ctrl.Reques
if awake && shouldSleep && !wakeRequestReceived {
log.Info("Going to sleep")
r.sleep(ctx, sleepSchedule)
log.Info("Successfully asleep")
} else if !awake && (!shouldSleep || wakeRequestReceived) {
log.Info("Waking up")
r.wake(ctx, sleepSchedule)
log.Info("Successfully awake")
}

// If the app should be awake, clear the sleep data
Expand Down Expand Up @@ -254,9 +279,9 @@ func (r *SleepScheduleReconciler) isAppAwake(ctx context.Context, sleepSchedule
}

// Return false if any deployment has 0 replicas
for _, deploymentName := range sleepSchedule.Spec.Deployments {
for _, deploy := range sleepSchedule.Spec.Deployments {
deployment := &appsv1.Deployment{}
err := r.Get(ctx, client.ObjectKey{Namespace: sleepSchedule.Namespace, Name: deploymentName}, deployment)
err := r.Get(ctx, client.ObjectKey{Namespace: sleepSchedule.Namespace, Name: deploy.Name}, deployment)
if err != nil {
return false, err
}
Expand All @@ -272,7 +297,7 @@ func (r *SleepScheduleReconciler) isAppAwake(ctx context.Context, sleepSchedule
func (r *SleepScheduleReconciler) wake(ctx context.Context, sleepSchedule *snorlaxv1beta1.SleepSchedule) error {
// Scale up each deployment
var wg sync.WaitGroup
for _, deploymentName := range sleepSchedule.Spec.Deployments {
for _, deploy := range sleepSchedule.Spec.Deployments {
wg.Add(1)
go func(name string) {
defer wg.Done()
Expand All @@ -287,21 +312,37 @@ func (r *SleepScheduleReconciler) wake(ctx context.Context, sleepSchedule *snorl
// Wake and wait for the deployment
r.scaleDeployment(ctx, sleepSchedule.Namespace, name, replicas)
r.waitForDeploymentToWake(ctx, sleepSchedule.Namespace, name)
}(deploymentName)
}(deploy.Name)
}

// Wait for all deployments to finish scaling up
wg.Wait()
// Have each ingress wait for its requirements and load the copy
for _, ing := range sleepSchedule.Spec.Ingresses {
wg.Add(1)
go func(ing snorlaxv1beta1.Ingress) error {
defer wg.Done()

// Load the ingress copies
for _, ingressName := range sleepSchedule.Spec.Ingresses {
err := r.loadIngressCopy(ctx, sleepSchedule, ingressName)
if err != nil {
log.FromContext(ctx).Error(err, "Failed to load Ingress copy")
return err
}
// Wait 2 seconds for the deployments to start scaling
time.Sleep(2 * time.Second)

// Wait for all requirements to be ready
r.waitForRequirementsToBeReady(ctx, &ing)

// Load the ingress copy
err := r.loadIngressCopy(ctx, sleepSchedule, ing.Name)
if err != nil {
log.FromContext(ctx).Error(err, "Failed to load Ingress copy")
return err
}

log.FromContext(ctx).Info(fmt.Sprintf("Ingress restored: %s", ing.Name))

return nil
}(ing)
}

// Wait for all deployments and ingresses to wake
wg.Wait()

// Delete the Snorlax proxy
err := r.DeleteSnorlaxProxy(ctx, sleepSchedule)
if err != nil {
Expand All @@ -317,15 +358,15 @@ func (r *SleepScheduleReconciler) sleep(ctx context.Context, sleepSchedule *snor
r.deploySnorlaxProxy(ctx, sleepSchedule)

// Point each ingress to the Snorlax proxy
for _, ingressName := range sleepSchedule.Spec.Ingresses {
r.takeIngressCopy(ctx, sleepSchedule, ingressName)
r.pointIngressToSnorlax(ctx, sleepSchedule, ingressName)
for _, ing := range sleepSchedule.Spec.Ingresses {
r.takeIngressCopy(ctx, sleepSchedule, ing.Name)
r.pointIngressToSnorlax(ctx, sleepSchedule, ing.Name)
}

// Scale down each deployment
for _, deploymentName := range sleepSchedule.Spec.Deployments {
r.storeCurrentReplicas(ctx, sleepSchedule, deploymentName)
r.scaleDeployment(ctx, sleepSchedule.Namespace, deploymentName, 0)
for _, deploy := range sleepSchedule.Spec.Deployments {
r.storeCurrentReplicas(ctx, sleepSchedule, deploy.Name)
r.scaleDeployment(ctx, sleepSchedule.Namespace, deploy.Name, 0)
}
}

Expand Down

0 comments on commit 58a5c30

Please sign in to comment.