Skip to content

Commit

Permalink
set cma managed by addon-manager if not configured
Browse files Browse the repository at this point in the history
Signed-off-by: haoqing0110 <qhao@redhat.com>
  • Loading branch information
haoqing0110 committed Mar 12, 2024
1 parent b1b734a commit 1f99fa2
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 12 deletions.
71 changes: 71 additions & 0 deletions pkg/addon/controllers/managementaddon/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package managementaddon

import (
"context"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog/v2"
addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
addonv1alpha1client "open-cluster-management.io/api/client/addon/clientset/versioned"
addoninformerv1alpha1 "open-cluster-management.io/api/client/addon/informers/externalversions/addon/v1alpha1"
addonlisterv1alpha1 "open-cluster-management.io/api/client/addon/listers/addon/v1alpha1"
"open-cluster-management.io/ocm/pkg/common/queue"
"open-cluster-management.io/sdk-go/pkg/patcher"

"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
)

// clusterManagementAddonController reconciles cma on the hub.
type clusterManagementAddonController struct {
patcher patcher.Patcher[
*addonv1alpha1.ClusterManagementAddOn, addonv1alpha1.ClusterManagementAddOnSpec, addonv1alpha1.ClusterManagementAddOnStatus]
clusterManagementAddonLister addonlisterv1alpha1.ClusterManagementAddOnLister
}

func NewManagementAddonController(
addonClient addonv1alpha1client.Interface,
clusterManagementAddonInformers addoninformerv1alpha1.ClusterManagementAddOnInformer,
recorder events.Recorder,
) factory.Controller {
c := &clusterManagementAddonController{
patcher: patcher.NewPatcher[
*addonv1alpha1.ClusterManagementAddOn, addonv1alpha1.ClusterManagementAddOnSpec, addonv1alpha1.ClusterManagementAddOnStatus](
addonClient.AddonV1alpha1().ClusterManagementAddOns()),
clusterManagementAddonLister: clusterManagementAddonInformers.Lister(),
}

return factory.New().WithInformersQueueKeysFunc(
queue.QueueKeyByMetaName,
clusterManagementAddonInformers.Informer()).
WithSync(c.sync).ToController("management-addon-controller", recorder)

}

func (c *clusterManagementAddonController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
addonName := syncCtx.QueueKey()
logger := klog.FromContext(ctx)
logger.V(4).Info("Reconciling addon", "addonName", addonName)

cma, err := c.clusterManagementAddonLister.Get(addonName)
switch {
case errors.IsNotFound(err):
return nil
case err != nil:
return err
}

// If cma annotation "addon.open-cluster-management.io/lifecycle: self" is not set,
// force add "addon.open-cluster-management.io/lifecycle: addon-manager" .
// The migration plan refer to https://github.com/open-cluster-management-io/ocm/issues/355.
cmaCopy := cma.DeepCopy()
if cmaCopy.Annotations == nil {
cmaCopy.Annotations = map[string]string{}
}
if cmaCopy.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey] != addonv1alpha1.AddonLifecycleSelfManageAnnotationValue {
cmaCopy.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey] = addonv1alpha1.AddonLifecycleAddonManagerAnnotationValue
}

_, err = c.patcher.PatchLabelAnnotations(ctx, cmaCopy, cmaCopy.ObjectMeta, cma.ObjectMeta)
return err
}
149 changes: 149 additions & 0 deletions pkg/addon/controllers/managementaddon/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package managementaddon

import (
"context"
"encoding/json"
"testing"
"time"

"k8s.io/apimachinery/pkg/runtime"
clienttesting "k8s.io/client-go/testing"
"open-cluster-management.io/addon-framework/pkg/addonmanager/addontesting"
"open-cluster-management.io/addon-framework/pkg/agent"
addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
fakeaddon "open-cluster-management.io/api/client/addon/clientset/versioned/fake"
addoninformers "open-cluster-management.io/api/client/addon/informers/externalversions"
clusterv1 "open-cluster-management.io/api/cluster/v1"
testingcommon "open-cluster-management.io/ocm/pkg/common/testing"
)

type testAgent struct {
name string
strategy *agent.InstallStrategy
}

func (t *testAgent) Manifests(cluster *clusterv1.ManagedCluster, addon *addonv1alpha1.ManagedClusterAddOn) ([]runtime.Object, error) {
return nil, nil
}

func (t *testAgent) GetAgentAddonOptions() agent.AgentAddonOptions {
return agent.AgentAddonOptions{
AddonName: t.name,
InstallStrategy: t.strategy,
}
}

func newClusterManagementAddonWithAnnotation(name string, annotations map[string]string) *addonv1alpha1.ClusterManagementAddOn {
cma := addontesting.NewClusterManagementAddon(name, "", "").Build()
cma.Annotations = annotations
return cma
}

func TestReconcile(t *testing.T) {
cases := []struct {
name string
syncKey string
cma []runtime.Object
testaddons map[string]agent.AgentAddon
validateAddonActions func(t *testing.T, actions []clienttesting.Action)
}{
{
name: "add annotation if no annotation",
syncKey: "test",
cma: []runtime.Object{newClusterManagementAddonWithAnnotation("test", nil)},
validateAddonActions: func(t *testing.T, actions []clienttesting.Action) {
addontesting.AssertActions(t, actions, "patch")
patch := actions[0].(clienttesting.PatchActionImpl).Patch
cma := &addonv1alpha1.ClusterManagementAddOn{}
err := json.Unmarshal(patch, cma)
if err != nil {
t.Fatal(err)
}

if len(cma.Annotations) != 1 || cma.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey] != addonv1alpha1.AddonLifecycleAddonManagerAnnotationValue {
t.Errorf("cma annotation is not correct, expected addon-manager but got %s", cma.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey])
}
},
testaddons: map[string]agent.AgentAddon{
"test": &testAgent{name: "test", strategy: agent.InstallAllStrategy("test")},
},
},
{
name: "add annotation if addon.open-cluster-management.io/lifecycle is empty",
syncKey: "test",
cma: []runtime.Object{newClusterManagementAddonWithAnnotation("test", map[string]string{
"test": "test",
addonv1alpha1.AddonLifecycleAnnotationKey: "",
})},
validateAddonActions: func(t *testing.T, actions []clienttesting.Action) {
addontesting.AssertActions(t, actions, "patch")
patch := actions[0].(clienttesting.PatchActionImpl).Patch
cma := &addonv1alpha1.ClusterManagementAddOn{}
err := json.Unmarshal(patch, cma)
if err != nil {
t.Fatal(err)
}

if len(cma.Annotations) != 1 || cma.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey] != addonv1alpha1.AddonLifecycleAddonManagerAnnotationValue {
t.Errorf("cma annotation is not correct, expected addon-manager but got %s", cma.Annotations[addonv1alpha1.AddonLifecycleAnnotationKey])
}
},
testaddons: map[string]agent.AgentAddon{
"test": &testAgent{name: "test", strategy: agent.InstallAllStrategy("test")},
},
},
{
name: "no patch annotation if managed by self",
syncKey: "test",
cma: []runtime.Object{newClusterManagementAddonWithAnnotation("test", map[string]string{
"test": "test",
addonv1alpha1.AddonLifecycleAnnotationKey: addonv1alpha1.AddonLifecycleSelfManageAnnotationValue,
})},
validateAddonActions: addontesting.AssertNoActions,
testaddons: map[string]agent.AgentAddon{
"test": &testAgent{name: "test", strategy: agent.InstallAllStrategy("test")},
},
},
{
name: "no patch annotation if managed by addon-manager",
syncKey: "test",
cma: []runtime.Object{newClusterManagementAddonWithAnnotation("test", map[string]string{
"test": "test",
addonv1alpha1.AddonLifecycleAnnotationKey: addonv1alpha1.AddonLifecycleAddonManagerAnnotationValue,
})},
validateAddonActions: addontesting.AssertNoActions,
testaddons: map[string]agent.AgentAddon{
"test": &testAgent{name: "test"},
},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
fakeAddonClient := fakeaddon.NewSimpleClientset(c.cma...)
addonInformers := addoninformers.NewSharedInformerFactory(fakeAddonClient, 10*time.Minute)

for _, obj := range c.cma {
if err := addonInformers.Addon().V1alpha1().ClusterManagementAddOns().Informer().GetStore().Add(obj); err != nil {
t.Fatal(err)
}
}

syncContext := testingcommon.NewFakeSyncContext(t, c.syncKey)
recorder := syncContext.Recorder()

controller := NewManagementAddonController(
fakeAddonClient,
addonInformers.Addon().V1alpha1().ClusterManagementAddOns(),
recorder,
)

err := controller.Sync(context.TODO(), syncContext)
if err != nil {
t.Errorf("expected no error when sync: %v", err)
}
c.validateAddonActions(t, fakeAddonClient.Actions())

})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
type managementAddonInstallProgressionController struct {
patcher patcher.Patcher[
*addonv1alpha1.ClusterManagementAddOn, addonv1alpha1.ClusterManagementAddOnSpec, addonv1alpha1.ClusterManagementAddOnStatus]
managedClusterAddonLister addonlisterv1alpha1.ManagedClusterAddOnLister
clusterManagementAddonLister addonlisterv1alpha1.ClusterManagementAddOnLister
addonFilterFunc factory.EventFilterFunc
}
Expand All @@ -38,7 +37,6 @@ func NewManagementAddonInstallProgressionController(
patcher: patcher.NewPatcher[
*addonv1alpha1.ClusterManagementAddOn, addonv1alpha1.ClusterManagementAddOnSpec, addonv1alpha1.ClusterManagementAddOnStatus](
addonClient.AddonV1alpha1().ClusterManagementAddOns()),
managedClusterAddonLister: addonInformers.Lister(),
clusterManagementAddonLister: clusterManagementAddonInformers.Lister(),
addonFilterFunc: addonFilterFunc,
}
Expand All @@ -64,15 +62,6 @@ func (c *managementAddonInstallProgressionController) sync(ctx context.Context,

mgmtAddonCopy := mgmtAddon.DeepCopy()

clusterManagementAddon, err := c.clusterManagementAddonLister.Get(addonName)
if errors.IsNotFound(err) {
return nil
}

if err != nil {
return err
}

// set default config reference
mgmtAddonCopy.Status.DefaultConfigReferences = setDefaultConfigReference(mgmtAddonCopy.Spec.SupportedConfigs, mgmtAddonCopy.Status.DefaultConfigReferences)

Expand All @@ -84,7 +73,7 @@ func (c *managementAddonInstallProgressionController) sync(ctx context.Context,
}

// only update default config references and skip updating install progression for self-managed addon
if !c.addonFilterFunc(clusterManagementAddon) {
if !c.addonFilterFunc(mgmtAddon) {
_, err = c.patcher.PatchStatus(ctx, mgmtAddonCopy, mgmtAddonCopy.Status, mgmtAddon.Status)
return err
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/addon/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"open-cluster-management.io/ocm/pkg/addon/controllers/addonowner"
"open-cluster-management.io/ocm/pkg/addon/controllers/addonprogressing"
"open-cluster-management.io/ocm/pkg/addon/controllers/addontemplate"
"open-cluster-management.io/ocm/pkg/addon/controllers/managementaddon"
"open-cluster-management.io/ocm/pkg/addon/controllers/managementaddoninstallprogression"
)

Expand Down Expand Up @@ -165,6 +166,15 @@ func RunControllerManagerWithInformers(
controllerContext.EventRecorder,
)

// This controller is used during migrating addons to be managed by addon-manager.
// This should be removed when the migration is done.
// The migration plan refer to https://github.com/open-cluster-management-io/ocm/issues/355.
managementAddonController := managementaddon.NewManagementAddonController(
hubAddOnClient,
addonInformers.Addon().V1alpha1().ClusterManagementAddOns(),
controllerContext.EventRecorder,
)

mgmtAddonInstallProgressionController := managementaddoninstallprogression.NewManagementAddonInstallProgressionController(
hubAddOnClient,
addonInformers.Addon().V1alpha1().ManagedClusterAddOns(),
Expand All @@ -188,6 +198,7 @@ func RunControllerManagerWithInformers(
go addonConfigurationController.Run(ctx, 2)
go addonOwnerController.Run(ctx, 2)
go addonProgressingController.Run(ctx, 2)
go managementAddonController.Run(ctx, 2)
go mgmtAddonInstallProgressionController.Run(ctx, 2)
// There should be only one instance of addonTemplateController running, since the addonTemplateController will
// start a goroutine for each template-type addon it watches.
Expand Down

0 comments on commit 1f99fa2

Please sign in to comment.