Skip to content

Commit

Permalink
Fixed issue with deployment targeting and ignoring previously targete…
Browse files Browse the repository at this point in the history
…d deployments.
  • Loading branch information
kristofferahl committed Dec 2, 2022
1 parent cad7af0 commit 307742f
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 34 deletions.
94 changes: 60 additions & 34 deletions controllers/sustainability/deployment_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"github.com/kristofferahl/aeto/internal/pkg/reconcile"
)

const (
DeploymentResourceApiVersion string = "apps/v1"
DeploymentResourceKind string = "Deployment"
)

type DeploymentResource struct {
deployments []appsv1.Deployment
replicas []DeploymentReplicas
Expand All @@ -28,15 +33,7 @@ func NewDeploymentResource(c kubernetes.Client, rctx reconcile.Context, savingsp
replicas: replicas,
}

hasDeployments := false
for _, target := range savingspolicy.Spec.Targets {
if target.ApiVersion == "apps/v1" && target.Kind == "Deployment" && !target.Ignore {
hasDeployments = true
break
}
}

if hasDeployments {
if hasDeploymentTargets(savingspolicy) {
var deployments appsv1.DeploymentList
if err := c.List(rctx, &deployments, &client.ListOptions{Namespace: rctx.Request.Namespace}); err != nil {
return r, err
Expand All @@ -45,18 +42,19 @@ func NewDeploymentResource(c kubernetes.Client, rctx reconcile.Context, savingsp
filtered := make([]appsv1.Deployment, 0)

for _, d := range deployments.Items {
ignore := false
for _, t := range savingspolicy.Spec.Targets {
if t.ApiVersion == "apps/v1" && t.Kind == "Deployment" && t.Name == d.Name && t.Ignore {
ignore = true
break
if ignoreDeploymentTarget(savingspolicy, d) {
replicas, found := r.originalReplicas(d.Name)
if found && replicas > 0 {
rctx.Log.V(1).Info("ignoring previously targeted deployment, trying wake up before ignoring", "deployment", d.Name, "previous-replicas", replicas)
err := r.wakeUpDeployment(c, rctx, d)
if err != nil {
rctx.Log.Error(err, "failed to wake up previously targeted deployment, will have to retry before ignoring", "deployment", d.Name, "previous-replicas", replicas)
return r, err
}
}
}

if !ignore {
filtered = append(filtered, d)
} else {
rctx.Log.V(1).Info("ignoring deployment", "deployment", d.Name)
} else {
filtered = append(filtered, d)
}
}

Expand All @@ -72,6 +70,7 @@ func (r DeploymentResource) HasResource() bool {

func (r DeploymentResource) Sleep(c kubernetes.Client, rctx reconcile.Context) error {
for _, d := range r.deployments {
rctx.Log.V(1).Info("ensuring deployment i scaled to 0", "deployment", d.Name)
if *d.Spec.Replicas != 0 {
if err := r.scaleTo(c.GetClient(), rctx, d, 0, *d.Spec.Replicas); err != nil {
return err
Expand All @@ -84,21 +83,9 @@ func (r DeploymentResource) Sleep(c kubernetes.Client, rctx reconcile.Context) e

func (r DeploymentResource) WakeUp(c kubernetes.Client, rctx reconcile.Context) error {
for _, d := range r.deployments {
if *d.Spec.Replicas != 0 {
rctx.Log.Info("deployment replicas not set to 0, skipping wake up", "deployment", d.Name)
continue
}

replicas, ok := r.originalReplicas(d.Name)
if !ok {
rctx.Log.Info("deployment not tracked in state, unable to wake up", "deployment", d.Name)
continue
}

if *d.Spec.Replicas != replicas {
if err := r.scaleTo(c.GetClient(), rctx, d, replicas, *d.Spec.Replicas); err != nil {
return err
}
err := r.wakeUpDeployment(c, rctx, d)
if err != nil {
return err
}
}

Expand All @@ -125,6 +112,27 @@ func (r DeploymentResource) Info() ([]byte, error) {
return json.Marshal(deploymentReplicas)
}

func (r DeploymentResource) wakeUpDeployment(c kubernetes.Client, rctx reconcile.Context, d appsv1.Deployment) error {
if *d.Spec.Replicas != 0 {
rctx.Log.V(1).Info("deployment replicas not set to 0, skipping wake up", "deployment", d.Name)
return nil
}

replicas, ok := r.originalReplicas(d.Name)
if !ok {
rctx.Log.Info("deployment not tracked in state, unable to wake up", "deployment", d.Name)
return nil
}

if *d.Spec.Replicas != replicas {
if err := r.scaleTo(c.GetClient(), rctx, d, replicas, *d.Spec.Replicas); err != nil {
return err
}
}

return nil
}

func (r DeploymentResource) originalReplicas(name string) (int32, bool) {
for _, r := range r.replicas {
if r.Name == name {
Expand Down Expand Up @@ -156,3 +164,21 @@ func ConvertToDeploymentsInfo(data []byte) ([]DeploymentReplicas, error) {

return deploymentReplicas, nil
}

func hasDeploymentTargets(savingspolicy sustainabilityv1alpha1.SavingsPolicy) bool {
for _, target := range savingspolicy.Spec.Targets {
if target.ApiVersion == DeploymentResourceApiVersion && target.Kind == DeploymentResourceKind {
return true
}
}
return false
}

func ignoreDeploymentTarget(savingspolicy sustainabilityv1alpha1.SavingsPolicy, deployment appsv1.Deployment) bool {
for _, t := range savingspolicy.Spec.Targets {
if t.ApiVersion == DeploymentResourceApiVersion && t.Kind == DeploymentResourceKind && (t.Name == "" || t.Name == deployment.Name) && t.Ignore {
return true
}
}
return false
}
193 changes: 193 additions & 0 deletions controllers/sustainability/deployment_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
Copyright 2022.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sustainability

import (
sustainabilityv1alpha1 "github.com/kristofferahl/aeto/apis/sustainability/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
//+kubebuilder:scaffold:imports
)

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.

var _ = Describe("Deployment Resource", func() {
Describe("SavingsPolicy", func() {
Describe("has deployment targets?", func() {
Context("with deployment targets in policy", func() {
sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: DeploymentResourceKind,
ApiVersion: DeploymentResourceApiVersion,
Ignore: true,
},
},
},
}
result := hasDeploymentTargets(sp)

It("should return true", func() {
Expect(result).To(BeTrue())
})
})

Context("with no deployment targets", func() {
sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: "Foobar",
ApiVersion: DeploymentResourceApiVersion,
Ignore: true,
},
},
},
}
result := hasDeploymentTargets(sp)

It("should return false", func() {
Expect(result).To(BeFalse())
})
})
})

Describe("ignore deployment target?", func() {
It("should return false when target matches all deployments", func() {
sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: DeploymentResourceKind,
ApiVersion: DeploymentResourceApiVersion,
Ignore: false,
},
},
},
}

d := appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: DeploymentResourceKind,
APIVersion: DeploymentResourceApiVersion,
},
ObjectMeta: v1.ObjectMeta{
Name: "foobar",
},
}

result := ignoreDeploymentTarget(sp, d)

Expect(result).To(BeFalse())
})

It("should return true when target matches all deployments but ignore is set to true", func() {
sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: DeploymentResourceKind,
ApiVersion: DeploymentResourceApiVersion,
Ignore: true,
},
},
},
}

d := appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: DeploymentResourceKind,
APIVersion: DeploymentResourceApiVersion,
},
ObjectMeta: v1.ObjectMeta{
Name: "foobar",
},
}

result := ignoreDeploymentTarget(sp, d)

Expect(result).To(BeTrue())
})

It("should return false when target matches deployment explicitly but ignore is set to false", func() {
name := "foobar"

sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: DeploymentResourceKind,
ApiVersion: DeploymentResourceApiVersion,
Name: name,
Ignore: false,
},
},
},
}

d := appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: DeploymentResourceKind,
APIVersion: DeploymentResourceApiVersion,
},
ObjectMeta: v1.ObjectMeta{
Name: name,
},
}

result := ignoreDeploymentTarget(sp, d)

Expect(result).To(BeFalse())
})

It("should return true when target matches deployment explicitly but ignore is set to true", func() {
name := "foobar"

sp := sustainabilityv1alpha1.SavingsPolicy{
Spec: sustainabilityv1alpha1.SavingsPolicySpec{
Targets: []sustainabilityv1alpha1.SavingsPolicyTarget{
{
Kind: DeploymentResourceKind,
ApiVersion: DeploymentResourceApiVersion,
Name: name,
Ignore: true,
},
},
},
}

d := appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: DeploymentResourceKind,
APIVersion: DeploymentResourceApiVersion,
},
ObjectMeta: v1.ObjectMeta{
Name: name,
},
}

result := ignoreDeploymentTarget(sp, d)

Expect(result).To(BeTrue())
})
})
})
})

0 comments on commit 307742f

Please sign in to comment.