Skip to content
This repository has been archived by the owner on Jun 8, 2022. It is now read-only.

Commit

Permalink
delete revisions
Browse files Browse the repository at this point in the history
Signed-off-by: erdun <[email protected]>
  • Loading branch information
erdun committed Aug 3, 2020
1 parent be76681 commit ace7cd5
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 16 deletions.
8 changes: 6 additions & 2 deletions cmd/oam-kubernetes-runtime/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/crossplane/oam-kubernetes-runtime/apis/core"
controller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
appController "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
webhook "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2"
)

Expand All @@ -34,6 +35,7 @@ func main() {
var certDir string
var webhookPort int
var useWebhook bool
var controllerArgs controller.Args

flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
flag.StringVar(&certDir, "webhook-cert-dir", "/k8s-webhook-server/serving-certs", "Admission webhook cert/key dir.")
Expand All @@ -44,6 +46,8 @@ func main() {
flag.StringVar(&logFilePath, "log-file-path", "", "The address the metric endpoint binds to.")
flag.IntVar(&logRetainDate, "log-retain-date", 7, "The number of days of logs history to retain.")
flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.")
flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 10,
"RevisionLimit is the maximum number of revisions that will be maintained. The default value is 10.")
flag.Parse()

// setup logging
Expand Down Expand Up @@ -82,7 +86,7 @@ func main() {
webhook.Add(mgr)
}

if err = controller.Setup(mgr, logging.NewLogrLogger(oamLog)); err != nil {
if err = appController.Setup(mgr, controllerArgs, logging.NewLogrLogger(oamLog)); err != nil {
oamLog.Error(err, "unable to setup the oam core controller")
os.Exit(1)
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Copyright 2020 The Crossplane Authors.
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 controller

// Args args used by controller
type Args struct {
// RevisionLimit is the maximum number of revisions that will be maintained.
// The default value is 10.
RevisionLimit int
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"

"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
)

const (
Expand Down Expand Up @@ -71,15 +72,16 @@ const (
)

// Setup adds a controller that reconciles ApplicationConfigurations.
func Setup(mgr ctrl.Manager, l logging.Logger) error {
func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error {
name := "oam/" + strings.ToLower(v1alpha2.ApplicationConfigurationGroupKind)

return ctrl.NewControllerManagedBy(mgr).
Named(name).
For(&v1alpha2.ApplicationConfiguration{}).
Watches(&source.Kind{Type: &v1alpha2.Component{}}, &ComponentHandler{
Client: mgr.GetClient(),
Logger: l,
Client: mgr.GetClient(),
Logger: l,
RevisionLimit: args.RevisionLimit,
}).
Complete(NewReconciler(mgr,
WithLogger(l.WithValues("controller", name)),
Expand Down
90 changes: 87 additions & 3 deletions pkg/controller/v1alpha2/applicationconfiguration/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"sort"
"strings"
"time"

Expand All @@ -22,10 +23,15 @@ import (
util "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)

// ControllerRevisionComponentLabel indicate which component the revision belong to
// This label is to filter revision by client api
const ControllerRevisionComponentLabel = "controller.oam.dev/component"

// ComponentHandler will watch component change and generate Revision automatically.
type ComponentHandler struct {
Client client.Client
Logger logging.Logger
Client client.Client
Logger logging.Logger
RevisionLimit int
}

// Create implements EventHandler
Expand Down Expand Up @@ -144,7 +150,8 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim
// set annotation to component
revision := appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Name: revisionName,
Name: revisionName,
Namespace: mt.GetNamespace(),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Expand All @@ -154,6 +161,9 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim
Controller: newTrue(),
},
},
Labels: map[string]string{
ControllerRevisionComponentLabel: curComp.Name,
},
},
Revision: nextRevision,
Data: runtime.RawExtension{Object: curComp},
Expand All @@ -169,9 +179,74 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim
return false
}
c.Logger.Info(fmt.Sprintf("ControllerRevision %s created", revisionName))
if int64(c.RevisionLimit) < nextRevision {
if err := c.cleanupControllerRevision(curComp); err != nil {
c.Logger.Info(fmt.Sprintf("failed to clean up revisions of Component %v.", err))
}
}
return true
}

// clean revisions when over limits
func (c *ComponentHandler) cleanupControllerRevision(curComp *v1alpha2.Component) error {
labels := &metav1.LabelSelector{
MatchLabels: map[string]string{
ControllerRevisionComponentLabel: curComp.Name,
},
}
selector, err := metav1.LabelSelectorAsSelector(labels)
if err != nil {
return err
}

// List and Get Object, controller-runtime will create Informer cache
// and will get objects from cache
revisions := &appsv1.ControllerRevisionList{}
if err := c.Client.List(context.TODO(), revisions, &client.ListOptions{LabelSelector: selector}); err != nil {
return err
}

// Get appConfigs and workloads filter controllerRevision used
appConfigs := &v1alpha2.ApplicationConfigurationList{}
if err := c.Client.List(context.Background(), appConfigs); err != nil {
return err
}

// get all revisions used and skipped
liveHashes := make(map[string]bool)
for _, appConfig := range appConfigs.Items {
for _, component := range appConfig.Spec.Components {
if component.RevisionName != "" {
liveHashes[component.RevisionName] = true
}
}
}

toKeep := c.RevisionLimit + len(liveHashes)
toKill := len(revisions.Items) - toKeep
if toKill <= 0 {
return nil
}
// Clean up old revisions from smallest to highest revision (from oldest to newest)
sort.Sort(historiesByRevision(revisions.Items))
for _, revision := range revisions.Items {
if toKill <= 0 {
break
}
if hash := revision.GetName(); liveHashes[hash] {
continue
}
// Clean up
revisionToClean := revision
if err := c.Client.Delete(context.TODO(), &revisionToClean); err != nil {
return err
}
c.Logger.Info(fmt.Sprintf("ControllerRevision %s deleted", revision.Name))
toKill--
}
return nil
}

// ConstructRevisionName will generate revisionName from componentName
// hash suffix char set added to componentName is (0-9, a-v)
func ConstructRevisionName(componentName string) string {
Expand All @@ -183,3 +258,12 @@ func ExtractComponentName(revisionName string) string {
splits := strings.Split(revisionName, "-")
return strings.Join(splits[0:len(splits)-1], "-")
}

// historiesByRevision sort controllerRevision by revision
type historiesByRevision []appsv1.ControllerRevision

func (h historiesByRevision) Len() int { return len(h) }
func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h historiesByRevision) Less(i, j int) bool {
return h[i].Revision < h[j].Revision
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,23 @@ func TestComponentHandler(t *testing.T) {
}
return nil
}),
MockDelete: test.NewMockDeleteFn(nil, func(obj runtime.Object) error {
if robj, ok := obj.(*appsv1.ControllerRevision); ok {
newRevisions := []appsv1.ControllerRevision{}
for _, revision := range createdRevisions {
if revision.Name == robj.Name {
continue
}

newRevisions = append(newRevisions, revision)
}
createdRevisions = newRevisions
}
return nil
}),
},
Logger: logging.NewLogrLogger(ctrl.Log.WithName("test")),
Logger: logging.NewLogrLogger(ctrl.Log.WithName("test")),
RevisionLimit: 2,
}
comp := &v1alpha2.Component{
ObjectMeta: metav1.ObjectMeta{Namespace: "biz", Name: "comp1"},
Expand Down Expand Up @@ -180,6 +195,28 @@ func TestComponentHandler(t *testing.T) {
t.Fatal("should not trigger event with nothing changed no change")
}
// ============ Test Update Event End ===================

// ============ Test Revisions Start ===================
// test clean revision
comp4 := &v1alpha2.Component{
ObjectMeta: metav1.ObjectMeta{Namespace: "biz", Name: "comp1", Labels: map[string]string{"bar": "foo"}},
Spec: v1alpha2.ComponentSpec{Workload: runtime.RawExtension{Object: &v1.Deployment{Spec: v1.DeploymentSpec{Template: v12.PodTemplateSpec{Spec: v12.PodSpec{Containers: []v12.Container{{Image: "nginx:v3"}}}}}}}},
}
curComp.Status.DeepCopyInto(&comp4.Status)
updateEvt = event.UpdateEvent{
ObjectOld: comp2,
MetaOld: comp2.GetObjectMeta(),
ObjectNew: comp4,
MetaNew: comp4.GetObjectMeta(),
}
instance.Update(updateEvt, q)
revisions = &appsv1.ControllerRevisionList{}
err = instance.Client.List(context.TODO(), revisions)
assert.NoError(t, err)
assert.Equal(t, 2, len(revisions.Items), "Expected has two revisions")
assert.Equal(t, "comp1", revisions.Items[0].Labels[ControllerRevisionComponentLabel],
fmt.Sprintf("Expected revision has label %s: comp1", ControllerRevisionComponentLabel))
// ============ Test Revisions End ===================
}

func TestConstructExtract(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource"

"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
)

const (
Expand All @@ -53,7 +54,7 @@ const (
)

// Setup adds a controller that reconciles HealthScope.
func Setup(mgr ctrl.Manager, l logging.Logger) error {
func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error {
name := "oam/" + strings.ToLower(v1alpha2.HealthScopeGroupKind)

return ctrl.NewControllerManagedBy(mgr).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)

Expand All @@ -49,7 +50,7 @@ const (
)

// Setup adds a controller that reconciles ContainerizedWorkload.
func Setup(mgr ctrl.Manager, log logging.Logger) error {
func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
DiscoveryClient: *discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)

Expand All @@ -47,7 +48,7 @@ const (
)

// Setup adds a controller that reconciles ContainerizedWorkload.
func Setup(mgr ctrl.Manager, log logging.Logger) error {
func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
log: ctrl.Log.WithName("ContainerizedWorkload"),
Expand Down
7 changes: 4 additions & 3 deletions pkg/controller/v1alpha2/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ import (

"github.com/crossplane/crossplane-runtime/pkg/logging"

"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/applicationconfiguration"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/scopes/healthscope"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/traits/manualscalertrait"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/workloads/containerizedworkload"
)

// Setup workload controllers.
func Setup(mgr ctrl.Manager, l logging.Logger) error {
for _, setup := range []func(ctrl.Manager, logging.Logger) error{
func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error {
for _, setup := range []func(ctrl.Manager, controller.Args, logging.Logger) error{
applicationconfiguration.Setup, containerizedworkload.Setup, manualscalertrait.Setup, healthscope.Setup,
} {
if err := setup(mgr, l); err != nil {
if err := setup(mgr, args, l); err != nil {
return err
}
}
Expand Down
3 changes: 2 additions & 1 deletion test/integration/appconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (

"github.com/crossplane/oam-kubernetes-runtime/apis/core"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
v1alph2controller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
)

Expand Down Expand Up @@ -239,7 +240,7 @@ func TestAppConfigController(t *testing.T) {

zl := zap.New(zap.UseDevMode(true))
log := logging.NewLogrLogger(zl.WithName("app-config"))
if err := v1alph2controller.Setup(i, log); err != nil {
if err := v1alph2controller.Setup(i, controller.Args{RevisionLimit: 10}, log); err != nil {
t.Fatal(err)
}

Expand Down

0 comments on commit ace7cd5

Please sign in to comment.