From 8dd52acfaed927db000311b883bfacb510fcbd6f Mon Sep 17 00:00:00 2001 From: Mohamed ElSerngawy Date: Mon, 6 Feb 2023 20:23:44 -0500 Subject: [PATCH] Add create,update and delete placeManifestWork (#177) Signed-off-by: melserngawy --- go.mod | 3 +- go.sum | 6 +- pkg/helper/helpers.go | 36 +- .../placemanifestwork_add_finalizer_test.go | 65 +++ .../placemanifestwork_controller.go | 9 +- .../placemanifestwork_controllers_test.go | 79 +++ .../placemanifestwork_deploy_reconcile.go | 150 +++++- .../placemanifestwork_deploy_test.go | 199 ++++++++ .../placemanifestwork_finalize_reconcile.go | 42 +- .../placemanifestwork_finalizer_test.go | 43 ++ .../placemanifestwork_index_test.go | 120 +++++ .../placemanifestwork_status_reconcile.go | 77 ++- .../placemanifestwork_status_test.go | 228 +++++++++ pkg/hub/test/helper.go | 79 +++ vendor/golang.org/x/exp/LICENSE | 27 + vendor/golang.org/x/exp/PATENTS | 22 + .../x/exp/constraints/constraints.go | 50 ++ vendor/golang.org/x/exp/slices/slices.go | 258 ++++++++++ vendor/golang.org/x/exp/slices/sort.go | 126 +++++ vendor/golang.org/x/exp/slices/zsortfunc.go | 479 +++++++++++++++++ .../golang.org/x/exp/slices/zsortordered.go | 481 ++++++++++++++++++ vendor/modules.txt | 12 +- .../versioned/fake/clientset_generated.go | 90 ++++ .../cluster/clientset/versioned/fake/doc.go | 4 + .../clientset/versioned/fake/register.go | 46 ++ .../versioned/typed/cluster/v1/fake/doc.go | 4 + .../cluster/v1/fake/fake_cluster_client.go | 24 + .../cluster/v1/fake/fake_managedcluster.go | 117 +++++ .../typed/cluster/v1alpha1/fake/doc.go | 4 + .../v1alpha1/fake/fake_addonplacementscore.go | 126 +++++ .../v1alpha1/fake/fake_cluster_client.go | 28 + .../v1alpha1/fake/fake_clusterclaim.go | 106 ++++ .../typed/cluster/v1beta1/fake/doc.go | 4 + .../v1beta1/fake/fake_cluster_client.go | 36 ++ .../v1beta1/fake/fake_managedclusterset.go | 117 +++++ .../fake/fake_managedclustersetbinding.go | 126 +++++ .../cluster/v1beta1/fake/fake_placement.go | 126 +++++ .../v1beta1/fake/fake_placementdecision.go | 126 +++++ .../typed/cluster/v1beta2/fake/doc.go | 4 + .../v1beta2/fake/fake_cluster_client.go | 28 + .../v1beta2/fake/fake_managedclusterset.go | 117 +++++ .../fake/fake_managedclustersetbinding.go | 126 +++++ .../utils/work/v1/workapplier/workapplier.go | 138 +++++ .../utils/work/v1/workapplier/workcache.go | 102 ++++ 44 files changed, 4146 insertions(+), 44 deletions(-) create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_add_finalizer_test.go create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controllers_test.go create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_test.go create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalizer_test.go create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_index_test.go create mode 100644 pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_test.go create mode 100644 pkg/hub/test/helper.go create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/clientset_generated.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/doc.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/register.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/doc.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_cluster_client.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_managedcluster.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/doc.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_addonplacementscore.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_cluster_client.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_clusterclaim.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/doc.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster_client.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclusterset.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclustersetbinding.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placement.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placementdecision.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/doc.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_cluster_client.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclusterset.go create mode 100644 vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclustersetbinding.go create mode 100644 vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workapplier.go create mode 100644 vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workcache.go diff --git a/go.mod b/go.mod index 675735173..d14a132fa 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 + golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 k8s.io/api v0.24.3 k8s.io/apiextensions-apiserver v0.24.3 k8s.io/apimachinery v0.24.3 @@ -22,7 +23,7 @@ require ( k8s.io/component-base v0.24.3 k8s.io/klog/v2 v2.70.1 k8s.io/utils v0.0.0-20220725171434-9bab9ef40391 - open-cluster-management.io/api v0.9.1-0.20230113024003-6529d52caeeb + open-cluster-management.io/api v0.9.1-0.20230119012002-efddffc71682 sigs.k8s.io/controller-runtime v0.12.3 ) diff --git a/go.sum b/go.sum index bf0771b2f..407b687cd 100644 --- a/go.sum +++ b/go.sum @@ -738,6 +738,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE= +golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1241,8 +1243,8 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -open-cluster-management.io/api v0.9.1-0.20230113024003-6529d52caeeb h1:I4YWWnSpMirqj/heYbu44WeKBK3MJGboLgr4MEJfygs= -open-cluster-management.io/api v0.9.1-0.20230113024003-6529d52caeeb/go.mod h1:6BB/Y6r3hXlPjpJgDwIs6Ubxyx/kXXOg6D9Cntg1I9E= +open-cluster-management.io/api v0.9.1-0.20230119012002-efddffc71682 h1:ty0A+e5vs8a3WiG+8uL4o3fyW91BzR7do9kD1vwNSxg= +open-cluster-management.io/api v0.9.1-0.20230119012002-efddffc71682/go.mod h1:6BB/Y6r3hXlPjpJgDwIs6Ubxyx/kXXOg6D9Cntg1I9E= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/helper/helpers.go b/pkg/helper/helpers.go index 88789cd1c..9b6180636 100644 --- a/pkg/helper/helpers.go +++ b/pkg/helper/helpers.go @@ -28,7 +28,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" - clusterv1beta1client "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1" + clusterlister "open-cluster-management.io/api/client/cluster/listers/cluster/v1beta1" workv1client "open-cluster-management.io/api/client/work/clientset/versioned/typed/work/v1" clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" workapiv1 "open-cluster-management.io/api/work/v1" @@ -327,17 +327,24 @@ func GuessObjectGroupVersionKind(object runtime.Object) (*schema.GroupVersionKin } // RemoveFinalizer removes a finalizer from the list. It mutates its input. -func RemoveFinalizer(object runtime.Object, finalizerName string) { - accessor, _ := meta.Accessor(object) +func RemoveFinalizer(object runtime.Object, finalizerName string) (finalizersUpdated bool) { + accessor, err := meta.Accessor(object) + if err != nil { + return false + } + finalizers := accessor.GetFinalizers() newFinalizers := []string{} for i := range finalizers { if finalizers[i] == finalizerName { + finalizersUpdated = true continue } newFinalizers = append(newFinalizers, finalizers[i]) } accessor.SetFinalizers(newFinalizers) + + return finalizersUpdated } // AppliedManifestworkQueueKeyFunc return manifestwork key from appliedmanifestwork @@ -530,30 +537,15 @@ func BuildResourceMeta( } type PlacementDecisionGetter struct { - Client clusterv1beta1client.ClusterV1beta1Client + Client clusterlister.PlacementDecisionLister } func (pdl PlacementDecisionGetter) List(selector labels.Selector, namespace string) ([]*clusterv1beta1.PlacementDecision, error) { - opts := metav1.ListOptions{ - LabelSelector: selector.String(), - } - - decisionList, err := pdl.Client.PlacementDecisions(namespace).List(context.Background(), opts) - if err != nil { - return nil, err - } - var decisions []*clusterv1beta1.PlacementDecision - for i := range decisionList.Items { - decisions = append(decisions, &decisionList.Items[i]) - } - return decisions, nil + return pdl.Client.PlacementDecisions(namespace).List(selector) } -func GetPlacement(client clusterv1beta1client.ClusterV1beta1Client, ctx context.Context, opts metav1.GetOptions, name string, ns string) (*clusterv1beta1.Placement, error) { - return client.Placements(ns).Get(ctx, name, opts) -} - -func GetClusters(client clusterv1beta1client.ClusterV1beta1Client, placement *clusterv1beta1.Placement, existingClusters sets.String) (sets.String, sets.String, error) { +// Get added and deleted clusters names +func GetClusters(client clusterlister.PlacementDecisionLister, placement *clusterv1beta1.Placement, existingClusters sets.String) (sets.String, sets.String, error) { pdtracker := clusterv1beta1.NewPlacementDecisionClustersTracker(placement, PlacementDecisionGetter{Client: client}, existingClusters) return pdtracker.Get() diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_add_finalizer_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_add_finalizer_test.go new file mode 100644 index 000000000..a799698d7 --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_add_finalizer_test.go @@ -0,0 +1,65 @@ +package placemanifestworkcontroller + +import ( + "context" + "golang.org/x/exp/slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" +) + +// Test add finalizer reconcile +func TestAddFinalizerReconcile(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + fakeClient := fakeclient.NewSimpleClientset(pmwTest) + + addFinalizerController := &addFinalizerReconciler{ + workClient: fakeClient, + } + + pmwTest, _, err := addFinalizerController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + if !slices.Contains(pmwTest.Finalizers, PlaceManifestWorkFinalizer) { + t.Fatal("Finalizer did not added") + } + + // Set placeManifestWork delete time AND remove finalizer + timeNow := metav1.Now() + pmwTest.DeletionTimestamp = &timeNow + pmwTest.Finalizers = []string{} + + // Run reconcile + pmwTest, _, err = addFinalizerController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check finalizer not exist + if slices.Contains(pmwTest.Finalizers, PlaceManifestWorkFinalizer) { + t.Fatal("Finalizer added to deleted placemanifestWork") + } +} + +func TestAddFinalizerTwiceReconcile(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Finalizers = []string{PlaceManifestWorkFinalizer} + fakeClient := fakeclient.NewSimpleClientset(pmwTest) + + addFinalizerController := &addFinalizerReconciler{ + workClient: fakeClient, + } + + pmwTest, _, err := addFinalizerController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check fakeClient Actions does not have update action + if len(fakeClient.Actions()) > 0 { + t.Fatal("PlaceMW finalizer should not be updated") + } +} diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controller.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controller.go index 69464ab09..31b1091bd 100644 --- a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controller.go +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controller.go @@ -22,6 +22,7 @@ import ( workinformerv1 "open-cluster-management.io/api/client/work/informers/externalversions/work/v1" workinformerv1alpha1 "open-cluster-management.io/api/client/work/informers/externalversions/work/v1alpha1" worklisterv1alpha1 "open-cluster-management.io/api/client/work/listers/work/v1alpha1" + "open-cluster-management.io/api/utils/work/v1/workapplier" workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" ) @@ -71,10 +72,12 @@ func NewPlaceManifestWorkController( placeManifestWorkIndexer: placeManifestWorkInformer.Informer().GetIndexer(), reconcilers: []placeManifestWorkReconcile{ - &finalizeReconciler{workClient: workClient, manifestWorkLister: manifestWorkInformer.Lister()}, + &finalizeReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(workClient, manifestWorkInformer.Lister()), + workClient: workClient, manifestWorkLister: manifestWorkInformer.Lister()}, &addFinalizerReconciler{workClient: workClient}, - &deployReconciler{workClient: workClient, manifestWorkLister: manifestWorkInformer.Lister(), placementLister: placementInformer.Lister(), placeDecisionLister: placeDecisionInformer.Lister()}, - &statusReconciler{workClient: workClient, manifestWorkLister: manifestWorkInformer.Lister(), placeDecisionLister: placeDecisionInformer.Lister()}, + &deployReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(workClient, manifestWorkInformer.Lister()), + manifestWorkLister: manifestWorkInformer.Lister(), placementLister: placementInformer.Lister(), placeDecisionLister: placeDecisionInformer.Lister()}, + &statusReconciler{manifestWorkLister: manifestWorkInformer.Lister()}, }, } diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controllers_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controllers_test.go new file mode 100644 index 000000000..7ccac82f6 --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_controllers_test.go @@ -0,0 +1,79 @@ +package placemanifestworkcontroller + +import ( + "context" + clienttesting "k8s.io/client-go/testing" + fakeclusterclient "open-cluster-management.io/api/client/cluster/clientset/versioned/fake" + clusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions" + fakeworkclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + workinformers "open-cluster-management.io/api/client/work/informers/externalversions" + "open-cluster-management.io/api/utils/work/v1/workapplier" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" + "time" +) + +func TestPlaceMWControllerPatchStatus(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Status.PlacedManifestWorkSummary.Total = 1 + mw, _ := CreateManifestWork(pmwTest, "cls1") + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest, mw) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + + placement, placementDecision := helpertest.CreateTestPlacement("place-test", "default", "cls1") + + fClusterClient := fakeclusterclient.NewSimpleClientset(placement, placementDecision) + clusterInformerFactory := clusterinformers.NewSharedInformerFactoryWithOptions(fClusterClient, 1*time.Second) + + if err := clusterInformerFactory.Cluster().V1beta1().Placements().Informer().GetStore().Add(placement); err != nil { + t.Fatal(err) + } + if err := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Informer().GetStore().Add(placementDecision); err != nil { + t.Fatal(err) + } + + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + placementLister := clusterInformerFactory.Cluster().V1beta1().Placements().Lister() + placementDecisionLister := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Lister() + + pmwController := &PlaceManifestWorkController{ + workClient: fWorkClient, + placeManifestWorkLister: workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Lister(), + placeManifestWorkIndexer: workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetIndexer(), + + reconcilers: []placeManifestWorkReconcile{ + &finalizeReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + workClient: fWorkClient, manifestWorkLister: mwLister}, + &addFinalizerReconciler{workClient: fWorkClient}, + &deployReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + manifestWorkLister: mwLister, placementLister: placementLister, placeDecisionLister: placementDecisionLister}, + &statusReconciler{manifestWorkLister: mwLister}, + }, + } + + // create new pmwTestNew with status + pmwTestNew := helpertest.CreateTestPlaceManifestWork("pmw-test-new", "default", "place-test-new") + pmwTestNew.Status.PlacedManifestWorkSummary.Total = pmwTest.Status.PlacedManifestWorkSummary.Total + 3 + + err := pmwController.patchPlaceManifestStatus(context.TODO(), pmwTest, pmwTestNew) + if err != nil { + t.Fatal(err) + } + + // Check kubeClient has a patch action to apply + actions := ([]clienttesting.Action)(fWorkClient.Actions()) + if len(actions) == 0 { + t.Fatal("fWorkClient Should have patch action ") + } + // Check placeMW patch name + if pmwTest.Name != actions[0].(clienttesting.PatchAction).GetName() { + t.Fatal("PlaceMW patch action not match ", actions[0]) + } +} diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_reconcile.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_reconcile.go index 24e3de06f..9e7a9a1b7 100644 --- a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_reconcile.go +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_reconcile.go @@ -2,21 +2,163 @@ package placemanifestworkcontroller import ( "context" + "fmt" + "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" clusterlister "open-cluster-management.io/api/client/cluster/listers/cluster/v1beta1" - workclientset "open-cluster-management.io/api/client/work/clientset/versioned" worklisterv1 "open-cluster-management.io/api/client/work/listers/work/v1" + "open-cluster-management.io/api/utils/work/v1/workapplier" + workv1 "open-cluster-management.io/api/work/v1" workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" + "open-cluster-management.io/work/pkg/helper" ) // deployReconciler is to manage ManifestWork based on the placement. type deployReconciler struct { - workClient workclientset.Interface + workApplier *workapplier.WorkApplier manifestWorkLister worklisterv1.ManifestWorkLister placeDecisionLister clusterlister.PlacementDecisionLister placementLister clusterlister.PlacementLister } func (d *deployReconciler) reconcile(ctx context.Context, pw *workapiv1alpha1.PlaceManifestWork) (*workapiv1alpha1.PlaceManifestWork, reconcileState, error) { - // TODO the main manifestwork create/update/delete logic, we may also need to manage the status condition for whether manifestwork is created here - return pw, reconcileContinue, nil + // Manifestwork create/update/delete logic. + placement, err := d.placementLister.Placements(pw.Namespace).Get(pw.Spec.PlacementRef.Name) + if err != nil { + if errors.IsNotFound(err) { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetPlacementDecisionVerified(workapiv1alpha1.PlacementDecisionNotFound, "")) + } + return pw, reconcileContinue, fmt.Errorf("Failed get placement %w", err) + } + + req, err := labels.NewRequirement(PlaceManifestWorkControllerNameLabelKey, selection.In, []string{pw.Name}) + if err != nil { + return pw, reconcileContinue, err + } + + selector := labels.NewSelector().Add(*req) + manifestWorks, err := d.manifestWorkLister.List(selector) + if err != nil { + return pw, reconcileContinue, err + } + + errs := []error{} + existingClusters := sets.NewString() + for _, mw := range manifestWorks { + existingClusters.Insert(mw.Namespace) + } + + addedClusters, deletedClusters, err := helper.GetClusters(d.placeDecisionLister, placement, existingClusters) + if err != nil { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetPlacementDecisionVerified(workapiv1alpha1.NotAsExpected, "")) + + return pw, reconcileContinue, utilerrors.NewAggregate(errs) + } + + // Create manifestWork for added clusters + for cls := range addedClusters { + mw, err := CreateManifestWork(pw, cls) + if err != nil { + errs = append(errs, err) + continue + } + + _, err = d.workApplier.Apply(ctx, mw) + if err != nil { + errs = append(errs, err) + } + } + + // Update manifestWorks in case there are changes at ManifestWork or PlaceManifestWork + for cls := range existingClusters { + // Delete manifestWork for deleted clusters + if deletedClusters.Has(cls) { + err = d.workApplier.Delete(ctx, cls, pw.Name) + if err != nil { + errs = append(errs, err) + } + continue + } + + mw, err := CreateManifestWork(pw, cls) + if err != nil { + errs = append(errs, err) + continue + } + + _, err = d.workApplier.Apply(ctx, mw) + if err != nil { + errs = append(errs, err) + } + } + + // Set the PlacedManifestWorkSummary + if pw.Status.PlacedManifestWorkSummary == (workapiv1alpha1.PlacedManifestWorkSummary{}) { + pw.Status.PlacedManifestWorkSummary = workapiv1alpha1.PlacedManifestWorkSummary{} + } + total := len(existingClusters) - len(deletedClusters) + len(addedClusters) + if total < 0 { + total = 0 + } + + pw.Status.PlacedManifestWorkSummary.Total = total + if total == 0 { + pw.Status.PlacedManifestWorkSummary.Applied = 0 + pw.Status.PlacedManifestWorkSummary.Available = 0 + pw.Status.PlacedManifestWorkSummary.Degraded = 0 + pw.Status.PlacedManifestWorkSummary.Progressing = 0 + apimeta.SetStatusCondition(&pw.Status.Conditions, GetPlacementDecisionVerified(workapiv1alpha1.PlacementDecisionEmpty, "")) + } else { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetPlacementDecisionVerified(workapiv1alpha1.AsExpected, "")) + } + + return pw, reconcileContinue, utilerrors.NewAggregate(errs) +} + +// Return only True status if there all clusters have manifests applied as expected +func GetManifestworkApplied(reason string, message string) metav1.Condition { + if reason == workapiv1alpha1.AsExpected { + return getCondition(string(workapiv1alpha1.ManifestworkApplied), reason, message, metav1.ConditionTrue) + } + + return getCondition(string(workapiv1alpha1.ManifestworkApplied), reason, message, metav1.ConditionFalse) + +} + +// Return only True status if there are clusters selected +func GetPlacementDecisionVerified(reason string, message string) metav1.Condition { + if reason == workapiv1alpha1.AsExpected { + return getCondition(string(workapiv1alpha1.PlacementDecisionVerified), reason, message, metav1.ConditionTrue) + } + + return getCondition(string(workapiv1alpha1.PlacementDecisionVerified), reason, message, metav1.ConditionFalse) +} + +func getCondition(conditionType string, reason string, message string, status metav1.ConditionStatus) metav1.Condition { + return metav1.Condition{ + Type: conditionType, + Reason: reason, + Message: message, + Status: status, + LastTransitionTime: metav1.Now(), + } +} + +func CreateManifestWork(pw *workapiv1alpha1.PlaceManifestWork, clusterNS string) (*workv1.ManifestWork, error) { + if clusterNS == "" { + return nil, fmt.Errorf("Invalid cluster namespace") + } + + return &workv1.ManifestWork{ + ObjectMeta: metav1.ObjectMeta{ + Name: pw.Name, + Namespace: clusterNS, + Labels: map[string]string{PlaceManifestWorkControllerNameLabelKey: pw.Name}, + }, + Spec: pw.Spec.ManifestWorkTemplate}, nil } diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_test.go new file mode 100644 index 000000000..90072b8d6 --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_deploy_test.go @@ -0,0 +1,199 @@ +package placemanifestworkcontroller + +import ( + "context" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclusterclient "open-cluster-management.io/api/client/cluster/clientset/versioned/fake" + clusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions" + fakeworkclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + workinformers "open-cluster-management.io/api/client/work/informers/externalversions" + "open-cluster-management.io/api/utils/work/v1/workapplier" + workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" + "time" +) + +func TestDeployReconcileAsExpected(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + mw, _ := CreateManifestWork(pmwTest, "cls1") + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest, mw) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + + // Adding cls2 cluster to placement to check manifestwork added + placement, placementDecision := helpertest.CreateTestPlacement("place-test", "default", "cls1", "cls2") + fClusterClient := fakeclusterclient.NewSimpleClientset(placement, placementDecision) + clusterInformerFactory := clusterinformers.NewSharedInformerFactoryWithOptions(fClusterClient, 1*time.Second) + + if err := clusterInformerFactory.Cluster().V1beta1().Placements().Informer().GetStore().Add(placement); err != nil { + t.Fatal(err) + } + if err := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Informer().GetStore().Add(placementDecision); err != nil { + t.Fatal(err) + } + + placementLister := clusterInformerFactory.Cluster().V1beta1().Placements().Lister() + placementDecisionLister := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Lister() + + pmwDeployController := deployReconciler{ + workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + manifestWorkLister: mwLister, + placeDecisionLister: placementDecisionLister, + placementLister: placementLister, + } + + pmwTest, _, err := pmwDeployController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check for the expected PlacedManifestWorkSummary + pmwSummery := workapiv1alpha1.PlacedManifestWorkSummary{ + Total: int(placement.Status.NumberOfSelectedClusters), + Applied: 0, + Available: 0, + Degraded: 0, + Progressing: 0, + } + + if pmwTest.Status.PlacedManifestWorkSummary != pmwSummery { + t.Fatal("PlacedManifestWorkSummary not as expected ", pmwTest.Status.PlacedManifestWorkSummary, pmwSummery) + } + + // Check the PlacedManifestWork conditions + placeCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.PlacementDecisionVerified)) + + if placeCondition == nil { + t.Fatal("Placement condition not found ", pmwTest.Status.Conditions) + } + + // Check placement condition status true + if placeCondition.Status == metav1.ConditionFalse { + t.Fatal("Placement condition not True ", placeCondition) + } + + // Check placement condition reason + if placeCondition.Reason != workapiv1alpha1.AsExpected { + t.Fatal("Placement condition Reason not match AsExpected ", placeCondition) + } +} + +func TestDeployReconcileAsPlacementDecisionEmpty(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Minute) + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + + // No clusters added to the placement + placement, placementDecision := helpertest.CreateTestPlacement("place-test", "default") + fClusterClient := fakeclusterclient.NewSimpleClientset(placement, placementDecision) + clusterInformerFactory := clusterinformers.NewSharedInformerFactoryWithOptions(fClusterClient, 1*time.Minute) + + if err := clusterInformerFactory.Cluster().V1beta1().Placements().Informer().GetStore().Add(placement); err != nil { + t.Fatal(err) + } + if err := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Informer().GetStore().Add(placementDecision); err != nil { + t.Fatal(err) + } + + placementLister := clusterInformerFactory.Cluster().V1beta1().Placements().Lister() + placementDecisionLister := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Lister() + + pmwDeployController := deployReconciler{ + workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + manifestWorkLister: mwLister, + placeDecisionLister: placementDecisionLister, + placementLister: placementLister, + } + + pmwTest, _, err := pmwDeployController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check for the expected PlacedManifestWorkSummary + pmwSummery := workapiv1alpha1.PlacedManifestWorkSummary{ + Total: int(placement.Status.NumberOfSelectedClusters), + Applied: 0, + Available: 0, + Degraded: 0, + Progressing: 0, + } + + if pmwTest.Status.PlacedManifestWorkSummary != pmwSummery { + t.Fatal("PlacedManifestWorkSummary not as expected ", pmwTest.Status.PlacedManifestWorkSummary, pmwSummery) + } + + // Check the PlacedManifestWork conditions + placeCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.PlacementDecisionVerified)) + + if placeCondition == nil { + t.Fatal("Placement condition not found ", pmwTest.Status.Conditions) + } + + // Check placement condition status is false + if placeCondition.Status == metav1.ConditionTrue { + t.Fatal("Placement condition status not False ", placeCondition) + } + + // Check placement condition reason is PlacementDecisionEmpty + if placeCondition.Reason != workapiv1alpha1.PlacementDecisionEmpty { + t.Fatal("Placement condition Reason not match PlacementDecisionEmpty ", placeCondition) + } +} + +func TestDeployReconcileAsPlacementNotExist(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-notexist") + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Minute) + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + + placement, _ := helpertest.CreateTestPlacement("place-test", "default") + fClusterClient := fakeclusterclient.NewSimpleClientset(placement) + clusterInformerFactory := clusterinformers.NewSharedInformerFactoryWithOptions(fClusterClient, 1*time.Minute) + + if err := clusterInformerFactory.Cluster().V1beta1().Placements().Informer().GetStore().Add(placement); err != nil { + t.Fatal(err) + } + + placementLister := clusterInformerFactory.Cluster().V1beta1().Placements().Lister() + placementDecisionLister := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Lister() + + pmwDeployController := deployReconciler{ + workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + manifestWorkLister: mwLister, + placeDecisionLister: placementDecisionLister, + placementLister: placementLister, + } + + pmwTest, _, err := pmwDeployController.reconcile(context.TODO(), pmwTest) + if err == nil { + t.Fatal("Expected Not Found Error ", err) + } + + // Check the PlacedManifestWork conditions + placeCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.PlacementDecisionVerified)) + + if placeCondition == nil { + t.Fatal("Placement condition not found ", pmwTest.Status.Conditions) + } + + // Check placement condition status is false + if placeCondition.Status == metav1.ConditionTrue { + t.Fatal("Placement condition status not False ", placeCondition) + } + + // Check placement condition reason is PlacementDecisionNotFound + if placeCondition.Reason != workapiv1alpha1.PlacementDecisionNotFound { + t.Fatal("Placement condition Reason not match PlacementDecisionEmpty ", placeCondition) + } +} diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalize_reconcile.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalize_reconcile.go index 6093ece11..2cfe763ea 100644 --- a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalize_reconcile.go +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalize_reconcile.go @@ -2,13 +2,20 @@ package placemanifestworkcontroller import ( "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + utilerrors "k8s.io/apimachinery/pkg/util/errors" workclientset "open-cluster-management.io/api/client/work/clientset/versioned" worklisterv1 "open-cluster-management.io/api/client/work/listers/work/v1" + "open-cluster-management.io/api/utils/work/v1/workapplier" workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" + "open-cluster-management.io/work/pkg/helper" ) -// finalizeReconciler is to finalize the placeManifestWork by deleteing all related manifestWorks. +// finalizeReconciler is to finalize the placeManifestWork by deleting all related manifestWorks. type finalizeReconciler struct { + workApplier *workapplier.WorkApplier workClient workclientset.Interface manifestWorkLister worklisterv1.ManifestWorkLister } @@ -31,14 +38,39 @@ func (f *finalizeReconciler) reconcile(ctx context.Context, pw *workapiv1alpha1. } if err := f.finalizePlaceManifestWork(ctx, pw); err != nil { - return pw, reconcileStop, err + return pw, reconcileContinue, err + } + + // Remove finalizer after delete all created Manifestworks + if helper.RemoveFinalizer(pw, PlaceManifestWorkFinalizer) { + _, err := f.workClient.WorkV1alpha1().PlaceManifestWorks(pw.Namespace).Update(ctx, pw, metav1.UpdateOptions{}) + if err != nil { + return pw, reconcileContinue, err + } } - // TODO remove finalizer finally return pw, reconcileStop, nil } func (m *finalizeReconciler) finalizePlaceManifestWork(ctx context.Context, placeManifestWork *workapiv1alpha1.PlaceManifestWork) error { - // TODO: Delete all ManifestWork owned by the given placeManifestWork - return nil + req, err := labels.NewRequirement(PlaceManifestWorkControllerNameLabelKey, selection.In, []string{placeManifestWork.Name}) + if err != nil { + return err + } + + selector := labels.NewSelector().Add(*req) + manifestWorks, err := m.manifestWorkLister.List(selector) + if err != nil { + return err + } + + errs := []error{} + for _, mw := range manifestWorks { + err = m.workApplier.Delete(ctx, mw.Namespace, mw.Name) + if err != nil { + errs = append(errs, err) + } + } + + return utilerrors.NewAggregate(errs) } diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalizer_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalizer_test.go new file mode 100644 index 000000000..3620028ba --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_finalizer_test.go @@ -0,0 +1,43 @@ +package placemanifestworkcontroller + +import ( + "context" + "golang.org/x/exp/slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + workinformers "open-cluster-management.io/api/client/work/informers/externalversions" + "open-cluster-management.io/api/utils/work/v1/workapplier" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" + "time" +) + +// Test finalize reconcile +func TestFinalizeReconcile(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + mw, _ := CreateManifestWork(pmwTest, "cluster1") + fakeClient := fakeclient.NewSimpleClientset(pmwTest, mw) + manifestWorkInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fakeClient, 1*time.Second) + mwLister := manifestWorkInformerFactory.Work().V1().ManifestWorks().Lister() + + finalizerController := finalizeReconciler{ + workClient: fakeClient, + manifestWorkLister: mwLister, + workApplier: workapplier.NewWorkApplierWithTypedClient(fakeClient, mwLister), + } + + // Set placeManifestWork delete time AND Set finalizer + timeNow := metav1.Now() + pmwTest.DeletionTimestamp = &timeNow + pmwTest.Finalizers = append(pmwTest.Finalizers, PlaceManifestWorkFinalizer) + + pmwTest, _, err := finalizerController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check pmwTest finalizer removed + if slices.Contains(pmwTest.Finalizers, PlaceManifestWorkFinalizer) { + t.Fatal("Finalizer not deleted", pmwTest.Finalizers) + } +} diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_index_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_index_test.go new file mode 100644 index 000000000..b69830d6c --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_index_test.go @@ -0,0 +1,120 @@ +package placemanifestworkcontroller + +import ( + "k8s.io/client-go/tools/cache" + fakeclusterclient "open-cluster-management.io/api/client/cluster/clientset/versioned/fake" + clusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions" + fakeworkclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + workinformers "open-cluster-management.io/api/client/work/informers/externalversions" + "open-cluster-management.io/api/utils/work/v1/workapplier" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" + "time" +) + +func TestPlaceMWControllerIndex(t *testing.T) { + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Status.PlacedManifestWorkSummary.Total = 1 + mw, _ := CreateManifestWork(pmwTest, "cls1") + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest, mw) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().AddIndexers( + cache.Indexers{placeManifestWorkByPlacement: indexPlacementManifestWorkByPlacement}) + + if err != nil { + t.Fatal(err) + } + + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + + placement, placementDecision := helpertest.CreateTestPlacement("place-test", "default", "cls1") + + fClusterClient := fakeclusterclient.NewSimpleClientset(placement, placementDecision) + clusterInformerFactory := clusterinformers.NewSharedInformerFactoryWithOptions(fClusterClient, 1*time.Second) + + if err := clusterInformerFactory.Cluster().V1beta1().Placements().Informer().GetStore().Add(placement); err != nil { + t.Fatal(err) + } + if err := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Informer().GetStore().Add(placementDecision); err != nil { + t.Fatal(err) + } + + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + placementLister := clusterInformerFactory.Cluster().V1beta1().Placements().Lister() + placementDecisionLister := clusterInformerFactory.Cluster().V1beta1().PlacementDecisions().Lister() + + pmwController := &PlaceManifestWorkController{ + workClient: fWorkClient, + placeManifestWorkLister: workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Lister(), + placeManifestWorkIndexer: workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetIndexer(), + + reconcilers: []placeManifestWorkReconcile{ + &finalizeReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + workClient: fWorkClient, manifestWorkLister: mwLister}, + &addFinalizerReconciler{workClient: fWorkClient}, + &deployReconciler{workApplier: workapplier.NewWorkApplierWithTypedClient(fWorkClient, mwLister), + manifestWorkLister: mwLister, placementLister: placementLister, placeDecisionLister: placementDecisionLister}, + &statusReconciler{manifestWorkLister: mwLister}, + }, + } + + // Check index key creation + placementKey, err := indexPlacementManifestWorkByPlacement(pmwTest) + if err != nil { + t.Fatal(err) + } + if len(placementKey) == 0 { + t.Fatal("Key not created", placementKey) + } + if placementKey[0] != pmwTest.Namespace+"/"+pmwTest.Spec.PlacementRef.Name { + t.Fatal("placement Key not match ", placementKey[0]) + } + + expectedKey := pmwTest.Namespace + "/" + pmwTest.Name + placeNotExist, placeDecisinNotExist := helpertest.CreateTestPlacement("place-notExist", "ns-notExist") + // Check placement Queue Keys + keys := pmwController.placementQueueKeysFunc(placement) + if len(keys) == 0 { + t.Fatal("placement index keys not exist") + } + if keys[0] != expectedKey { + t.Fatal("Expected placement key not match ", keys[0], " - ", expectedKey) + } + // Check placement Queue Keys not exist + keys = pmwController.placementQueueKeysFunc(placeNotExist) + if len(keys) > 0 { + t.Fatal("placement index keys should not exist ", keys) + } + + // Check placementDecision Queue Keys + keys = pmwController.placementDecisionQueueKeysFunc(placementDecision) + if len(keys) == 0 { + t.Fatal("placement decision index keys not exist") + } + if keys[0] != expectedKey { + t.Fatal("Expected placementDecision key not match ", keys[0], " - ", expectedKey) + } + // Check placementDecision Queue Keys not exist + keys = pmwController.placementDecisionQueueKeysFunc(placeDecisinNotExist) + if len(keys) > 0 { + t.Fatal("placement decision index keys should not exist ", keys) + } + + // Check manifestWork Queue Keys + key := pmwController.manifestWorkQueueKeyFunc(mw) + if key != pmwTest.Name { + t.Fatal("Expected manifestwork key not match", key, " - ", pmwTest.Name) + } + // Check manifestWork Queue Keys not exist + mw.Labels = map[string]string{"testLabel": "label1"} + key = pmwController.manifestWorkQueueKeyFunc(mw) + if key != "" { + t.Fatal("Expected manifestwork key should not exist ", key) + } +} diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_reconcile.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_reconcile.go index 3146c9a3c..3ac10e952 100644 --- a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_reconcile.go +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_reconcile.go @@ -2,20 +2,85 @@ package placemanifestworkcontroller import ( "context" - clusterlister "open-cluster-management.io/api/client/cluster/listers/cluster/v1beta1" - workclientset "open-cluster-management.io/api/client/work/clientset/versioned" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" worklisterv1 "open-cluster-management.io/api/client/work/listers/work/v1" + workapiv1 "open-cluster-management.io/api/work/v1" workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" ) // statusReconciler is to update placeManifestwork status. type statusReconciler struct { - workClient workclientset.Interface - manifestWorkLister worklisterv1.ManifestWorkLister - placeDecisionLister clusterlister.PlacementDecisionLister + manifestWorkLister worklisterv1.ManifestWorkLister } func (d *statusReconciler) reconcile(ctx context.Context, pw *workapiv1alpha1.PlaceManifestWork) (*workapiv1alpha1.PlaceManifestWork, reconcileState, error) { - // TODO the logic for update placeManifestwork status + // The logic for update placeManifestwork status + if pw.Status.PlacedManifestWorkSummary.Total == 0 { + condition := apimeta.FindStatusCondition(pw.Status.Conditions, string(workapiv1alpha1.PlacementDecisionVerified)) + if condition != nil && condition.Reason == workapiv1alpha1.PlacementDecisionEmpty { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetManifestworkApplied(workapiv1alpha1.PlacementDecisionEmpty, "")) + } else { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetManifestworkApplied(workapiv1alpha1.NotAsExpected, "")) + } + + return pw, reconcileContinue, nil + } + + req, err := labels.NewRequirement(PlaceManifestWorkControllerNameLabelKey, selection.In, []string{pw.Name}) + if err != nil { + return pw, reconcileContinue, err + } + + selector := labels.NewSelector().Add(*req) + manifestWorks, err := d.manifestWorkLister.List(selector) + if err != nil { + return pw, reconcileContinue, err + } + + appliedCount, availableCount, degradCount, processingCount := 0, 0, 0, 0 + for _, mw := range manifestWorks { + if !mw.DeletionTimestamp.IsZero() { + continue + } + + // applied condition + condition := apimeta.FindStatusCondition(mw.Status.Conditions, string(workapiv1.ManifestApplied)) + if condition != nil && condition.Status == metav1.ConditionTrue { + appliedCount++ + } + // Progressing condition + condition = apimeta.FindStatusCondition(mw.Status.Conditions, string(workapiv1.ManifestProgressing)) + if condition != nil && condition.Status == metav1.ConditionTrue { + processingCount++ + } + // Available condition + condition = apimeta.FindStatusCondition(mw.Status.Conditions, string(workapiv1.ManifestAvailable)) + if condition != nil && condition.Status == metav1.ConditionTrue { + availableCount++ + } + // Degraded condition + condition = apimeta.FindStatusCondition(mw.Status.Conditions, string(workapiv1.ManifestDegraded)) + if condition != nil && condition.Status == metav1.ConditionTrue { + degradCount++ + } + } + + pw.Status.PlacedManifestWorkSummary.Available = availableCount + pw.Status.PlacedManifestWorkSummary.Degraded = degradCount + pw.Status.PlacedManifestWorkSummary.Progressing = processingCount + pw.Status.PlacedManifestWorkSummary.Applied = appliedCount + + if pw.Status.PlacedManifestWorkSummary.Available == pw.Status.PlacedManifestWorkSummary.Total && + pw.Status.PlacedManifestWorkSummary.Progressing == 0 && pw.Status.PlacedManifestWorkSummary.Degraded == 0 { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetManifestworkApplied(workapiv1alpha1.AsExpected, "")) + } else if pw.Status.PlacedManifestWorkSummary.Progressing > 0 && pw.Status.PlacedManifestWorkSummary.Degraded == 0 { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetManifestworkApplied(workapiv1alpha1.Processing, "")) + } else { + apimeta.SetStatusCondition(&pw.Status.Conditions, GetManifestworkApplied(workapiv1alpha1.NotAsExpected, "")) + } + return pw, reconcileContinue, nil } diff --git a/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_test.go b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_test.go new file mode 100644 index 000000000..d528c3e2c --- /dev/null +++ b/pkg/hub/controllers/placemanifestworkcontroller/placemanifestwork_status_test.go @@ -0,0 +1,228 @@ +package placemanifestworkcontroller + +import ( + "context" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeworkclient "open-cluster-management.io/api/client/work/clientset/versioned/fake" + workinformers "open-cluster-management.io/api/client/work/informers/externalversions" + workv1 "open-cluster-management.io/api/work/v1" + workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" + helpertest "open-cluster-management.io/work/pkg/hub/test" + "testing" + "time" +) + +func TestStatusReconcileAsExpected(t *testing.T) { + clusters := []string{"cls1", "cls2", "cls3", "cls4"} + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Status.PlacedManifestWorkSummary.Total = len(clusters) + + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + + for _, cls := range clusters { + mw, _ := CreateManifestWork(pmwTest, cls) + cond := getCondition(string(workv1.ManifestApplied), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + + cond = getCondition(string(workv1.ManifestAvailable), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + } + + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + pmwStatusController := statusReconciler{ + manifestWorkLister: mwLister, + } + + pmwTest, _, err := pmwStatusController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check for the expected PlacedManifestWorkSummary + pmwSummery := workapiv1alpha1.PlacedManifestWorkSummary{ + Total: len(clusters), + Applied: len(clusters), + Available: len(clusters), + Degraded: 0, + Progressing: 0, + } + + if pmwTest.Status.PlacedManifestWorkSummary != pmwSummery { + t.Fatal("PlacedManifestWorkSummary not as expected ", pmwTest.Status.PlacedManifestWorkSummary, pmwSummery) + } + + // Check the ManifestworkApplied conditions + appliedCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.ManifestworkApplied)) + + if appliedCondition == nil { + t.Fatal("Applied condition not found ", pmwTest.Status.Conditions) + } + + // Check ManifestworkApplied condition status true + if appliedCondition.Status == metav1.ConditionFalse { + t.Fatal("Applied condition not True ", appliedCondition) + } + + // Check ManifestworkApplied condition reason + if appliedCondition.Reason != workapiv1alpha1.AsExpected { + t.Fatal("Applied condition Reason not match AsExpected ", appliedCondition) + } +} + +func TestStatusReconcileAsProcessing(t *testing.T) { + clusters := []string{"cls1", "cls2", "cls3", "cls4"} + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Status.PlacedManifestWorkSummary.Total = len(clusters) + + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + + for id, cls := range clusters { + mw, _ := CreateManifestWork(pmwTest, cls) + cond := getCondition(string(workv1.ManifestApplied), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + + if id%2 == 0 { + cond = getCondition(string(workv1.ManifestAvailable), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + } else { + cond = getCondition(string(workv1.ManifestProgressing), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + } + + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + } + + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + pmwStatusController := statusReconciler{ + manifestWorkLister: mwLister, + } + + pmwTest, _, err := pmwStatusController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check for the expected PlacedManifestWorkSummary + pmwSummery := workapiv1alpha1.PlacedManifestWorkSummary{ + Total: len(clusters), + Applied: len(clusters), + Available: len(clusters) / 2, + Degraded: 0, + Progressing: len(clusters) / 2, + } + + if pmwTest.Status.PlacedManifestWorkSummary != pmwSummery { + t.Fatal("PlacedManifestWorkSummary not as expected ", pmwTest.Status.PlacedManifestWorkSummary, pmwSummery) + } + + // Check the ManifestworkApplied conditions + appliedCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.ManifestworkApplied)) + + if appliedCondition == nil { + t.Fatal("Applied condition not found ", pmwTest.Status.Conditions) + } + + // Check ManifestworkApplied condition status false + if appliedCondition.Status == metav1.ConditionTrue { + t.Fatal("Applied condition not True ", appliedCondition) + } + + // Check ManifestworkApplied condition reason + if appliedCondition.Reason != workapiv1alpha1.Processing { + t.Fatal("Applied condition Reason not match Processing ", appliedCondition) + } +} + +func TestStatusReconcileNotAsExpected(t *testing.T) { + clusters := []string{"cls1", "cls2", "cls3", "cls4"} + pmwTest := helpertest.CreateTestPlaceManifestWork("pmw-test", "default", "place-test") + pmwTest.Status.PlacedManifestWorkSummary.Total = len(clusters) + + fWorkClient := fakeworkclient.NewSimpleClientset(pmwTest) + workInformerFactory := workinformers.NewSharedInformerFactoryWithOptions(fWorkClient, 1*time.Second) + + if err := workInformerFactory.Work().V1alpha1().PlaceManifestWorks().Informer().GetStore().Add(pmwTest); err != nil { + t.Fatal(err) + } + + avaCount, processingCount, degradCount := 0, 0, 0 + for id, cls := range clusters { + mw, _ := CreateManifestWork(pmwTest, cls) + cond := getCondition(string(workv1.ManifestApplied), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + + if id%2 == 0 { + cond = getCondition(string(workv1.ManifestAvailable), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + avaCount++ + } else if id%3 == 0 { + cond = getCondition(string(workv1.ManifestDegraded), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + processingCount++ + } else { + cond = getCondition(string(workv1.ManifestProgressing), "", "", metav1.ConditionTrue) + apimeta.SetStatusCondition(&mw.Status.Conditions, cond) + degradCount++ + } + + if err := workInformerFactory.Work().V1().ManifestWorks().Informer().GetStore().Add(mw); err != nil { + t.Fatal(err) + } + } + + mwLister := workInformerFactory.Work().V1().ManifestWorks().Lister() + pmwStatusController := statusReconciler{ + manifestWorkLister: mwLister, + } + + pmwTest, _, err := pmwStatusController.reconcile(context.TODO(), pmwTest) + if err != nil { + t.Fatal(err) + } + + // Check for the expected PlacedManifestWorkSummary + pmwSummery := workapiv1alpha1.PlacedManifestWorkSummary{ + Total: len(clusters), + Applied: len(clusters), + Available: avaCount, + Degraded: degradCount, + Progressing: processingCount, + } + + if pmwTest.Status.PlacedManifestWorkSummary != pmwSummery { + t.Fatal("PlacedManifestWorkSummary not as expected ", pmwTest.Status.PlacedManifestWorkSummary, pmwSummery) + } + + // Check the ManifestworkApplied conditions + appliedCondition := apimeta.FindStatusCondition(pmwTest.Status.Conditions, string(workapiv1alpha1.ManifestworkApplied)) + + if appliedCondition == nil { + t.Fatal("Applied condition not found ", pmwTest.Status.Conditions) + } + + // Check ManifestworkApplied condition status false + if appliedCondition.Status == metav1.ConditionTrue { + t.Fatal("Applied condition not True ", appliedCondition) + } + + // Check ManifestworkApplied condition reason + if appliedCondition.Reason != workapiv1alpha1.NotAsExpected { + t.Fatal("Applied condition Reason not match NotAsExpected ", appliedCondition) + } +} diff --git a/pkg/hub/test/helper.go b/pkg/hub/test/helper.go new file mode 100644 index 000000000..4a4b3c5b4 --- /dev/null +++ b/pkg/hub/test/helper.go @@ -0,0 +1,79 @@ +package test + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" + workapiv1 "open-cluster-management.io/api/work/v1" + workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1" +) + +func CreateTestPlaceManifestWork(name string, ns string, placementName string) *workapiv1alpha1.PlaceManifestWork { + pmw := &workapiv1alpha1.PlaceManifestWork{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + ResourceVersion: "316679655", + UID: "0b1441ec-717f-4877-a165-27e5b59245f5", + }, + Spec: workapiv1alpha1.PlaceManifestWorkSpec{ + ManifestWorkTemplate: workapiv1.ManifestWorkSpec{ + Workload: workapiv1.ManifestsTemplate{ + Manifests: []workapiv1.Manifest{}, + }, + }, + PlacementRef: workapiv1alpha1.LocalPlacementReference{ + Name: placementName, + }, + }, + } + return pmw +} + +// Return placement with predicate of label cluster name +func CreateTestPlacement(name string, ns string, clusters ...string) (*clusterv1beta1.Placement, *clusterv1beta1.PlacementDecision) { + namereq := metav1.LabelSelectorRequirement{} + namereq.Key = "name" + namereq.Operator = metav1.LabelSelectorOpIn + for _, cls := range clusters { + namereq.Values = append(namereq.Values, cls) + } + + labelSelector := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{namereq}, + } + + clusterPredicate := clusterv1beta1.ClusterPredicate{ + RequiredClusterSelector: clusterv1beta1.ClusterSelector{ + LabelSelector: *labelSelector, + }, + } + + placement := &clusterv1beta1.Placement{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + Spec: clusterv1beta1.PlacementSpec{ + Predicates: []clusterv1beta1.ClusterPredicate{clusterPredicate}, + }, + } + placement.Status.NumberOfSelectedClusters = int32(len(clusters)) + + placementDecision := &clusterv1beta1.PlacementDecision{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-decision", + Namespace: ns, + Labels: map[string]string{clusterv1beta1.PlacementLabel: name}, + }, + } + + decisions := []clusterv1beta1.ClusterDecision{} + for _, cls := range clusters { + decisions = append(decisions, clusterv1beta1.ClusterDecision{ + ClusterName: cls, + }) + } + placementDecision.Status.Decisions = decisions + + return placement, placementDecision +} diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/golang.org/x/exp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/exp/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go new file mode 100644 index 000000000..2c033dff4 --- /dev/null +++ b/vendor/golang.org/x/exp/constraints/constraints.go @@ -0,0 +1,50 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package constraints defines a set of useful constraints to be used +// with type parameters. +package constraints + +// Signed is a constraint that permits any signed integer type. +// If future releases of Go add new predeclared signed integer types, +// this constraint will be modified to include them. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +// If future releases of Go add new predeclared unsigned integer types, +// this constraint will be modified to include them. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +// If future releases of Go add new predeclared integer types, +// this constraint will be modified to include them. +type Integer interface { + Signed | Unsigned +} + +// Float is a constraint that permits any floating-point type. +// If future releases of Go add new predeclared floating-point types, +// this constraint will be modified to include them. +type Float interface { + ~float32 | ~float64 +} + +// Complex is a constraint that permits any complex numeric type. +// If future releases of Go add new predeclared complex numeric types, +// this constraint will be modified to include them. +type Complex interface { + ~complex64 | ~complex128 +} + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +type Ordered interface { + Integer | Float | ~string +} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go new file mode 100644 index 000000000..cff0cd49e --- /dev/null +++ b/vendor/golang.org/x/exp/slices/slices.go @@ -0,0 +1,258 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slices defines various functions useful with slices of any type. +// Unless otherwise specified, these functions all apply to the elements +// of a slice at index 0 <= i < len(s). +// +// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a +// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings), +// or the sorting may fail to sort correctly. A common case is when sorting slices of +// floating-point numbers containing NaN values. +package slices + +import "golang.org/x/exp/constraints" + +// Equal reports whether two slices are equal: the same length and all +// elements equal. If the lengths are different, Equal returns false. +// Otherwise, the elements are compared in increasing index order, and the +// comparison stops at the first unequal pair. +// Floating point NaNs are not considered equal. +func Equal[E comparable](s1, s2 []E) bool { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + return true +} + +// EqualFunc reports whether two slices are equal using a comparison +// function on each pair of elements. If the lengths are different, +// EqualFunc returns false. Otherwise, the elements are compared in +// increasing index order, and the comparison stops at the first index +// for which eq returns false. +func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + v2 := s2[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Compare compares the elements of s1 and s2. +// The elements are compared sequentially, starting at index 0, +// until one element is not equal to the other. +// The result of comparing the first non-matching elements is returned. +// If both slices are equal until one of them ends, the shorter slice is +// considered less than the longer one. +// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. +// Comparisons involving floating point NaNs are ignored. +func Compare[E constraints.Ordered](s1, s2 []E) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + switch { + case v1 < v2: + return -1 + case v1 > v2: + return +1 + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// CompareFunc is like Compare but uses a comparison function +// on each pair of elements. The elements are compared in increasing +// index order, and the comparisons stop after the first time cmp +// returns non-zero. +// The result is the first non-zero result of cmp; if cmp always +// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), +// and +1 if len(s1) > len(s2). +func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + if c := cmp(v1, v2); c != 0 { + return c + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// Index returns the index of the first occurrence of v in s, +// or -1 if not present. +func Index[E comparable](s []E, v E) int { + for i, vs := range s { + if v == vs { + return i + } + } + return -1 +} + +// IndexFunc returns the first index i satisfying f(s[i]), +// or -1 if none do. +func IndexFunc[E any](s []E, f func(E) bool) int { + for i, v := range s { + if f(v) { + return i + } + } + return -1 +} + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + return Index(s, v) >= 0 +} + +// ContainsFunc reports whether at least one +// element e of s satisfies f(e). +func ContainsFunc[E any](s []E, f func(E) bool) bool { + return IndexFunc(s, f) >= 0 +} + +// Insert inserts the values v... into s at index i, +// returning the modified slice. +// In the returned slice r, r[i] == v[0]. +// Insert panics if i is out of range. +// This function is O(len(s) + len(v)). +func Insert[S ~[]E, E any](s S, i int, v ...E) S { + tot := len(s) + len(v) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[i:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[i:]) + return s2 +} + +// Delete removes the elements s[i:j] from s, returning the modified slice. +// Delete panics if s[i:j] is not a valid slice of s. +// Delete modifies the contents of the slice s; it does not create a new slice. +// Delete is O(len(s)-j), so if many items must be deleted, it is better to +// make a single call deleting them all together than to delete one at a time. +// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those +// elements contain pointers you might consider zeroing those elements so that +// objects they reference can be garbage collected. +func Delete[S ~[]E, E any](s S, i, j int) S { + _ = s[i:j] // bounds check + + return append(s[:i], s[j:]...) +} + +// Replace replaces the elements s[i:j] by the given v, and returns the +// modified slice. Replace panics if s[i:j] is not a valid slice of s. +func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { + _ = s[i:j] // verify that i:j is a valid subslice + tot := len(s[:i]) + len(v) + len(s[j:]) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[j:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[j:]) + return s2 +} + +// Clone returns a copy of the slice. +// The elements are copied using assignment, so this is a shallow clone. +func Clone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), s...) +} + +// Compact replaces consecutive runs of equal elements with a single copy. +// This is like the uniq command found on Unix. +// Compact modifies the contents of the slice s; it does not create a new slice. +// When Compact discards m elements in total, it might not modify the elements +// s[len(s)-m:len(s)]. If those elements contain pointers you might consider +// zeroing those elements so that objects they reference can be garbage collected. +func Compact[S ~[]E, E comparable](s S) S { + if len(s) < 2 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if v != last { + s[i] = v + i++ + last = v + } + } + return s[:i] +} + +// CompactFunc is like Compact but uses a comparison function. +func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { + if len(s) < 2 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if !eq(v, last) { + s[i] = v + i++ + last = v + } + } + return s[:i] +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. If n is negative or too large to +// allocate the memory, Grow panics. +func Grow[S ~[]E, E any](s S, n int) S { + if n < 0 { + panic("cannot be negative") + } + if n -= cap(s) - len(s); n > 0 { + // TODO(https://go.dev/issue/53888): Make using []E instead of S + // to workaround a compiler bug where the runtime.growslice optimization + // does not take effect. Revert when the compiler is fixed. + s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] + } + return s +} + +// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. +func Clip[S ~[]E, E any](s S) S { + return s[:len(s):len(s)] +} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go new file mode 100644 index 000000000..c5e4a6ac0 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/sort.go @@ -0,0 +1,126 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "math/bits" + + "golang.org/x/exp/constraints" +) + +// Sort sorts a slice of any ordered type in ascending order. +// Sort may fail to sort correctly when sorting slices of floating-point +// numbers containing Not-a-number (NaN) values. +// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))}) +// instead if the input may contain NaNs. +func Sort[E constraints.Ordered](x []E) { + n := len(x) + pdqsortOrdered(x, 0, n, bits.Len(uint(n))) +} + +// SortFunc sorts the slice x in ascending order as determined by the less function. +// This sort is not guaranteed to be stable. +// +// SortFunc requires that less is a strict weak ordering. +// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. +func SortFunc[E any](x []E, less func(a, b E) bool) { + n := len(x) + pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less) +} + +// SortStableFunc sorts the slice x while keeping the original order of equal +// elements, using less to compare elements. +func SortStableFunc[E any](x []E, less func(a, b E) bool) { + stableLessFunc(x, len(x), less) +} + +// IsSorted reports whether x is sorted in ascending order. +func IsSorted[E constraints.Ordered](x []E) bool { + for i := len(x) - 1; i > 0; i-- { + if x[i] < x[i-1] { + return false + } + } + return true +} + +// IsSortedFunc reports whether x is sorted in ascending order, with less as the +// comparison function. +func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool { + for i := len(x) - 1; i > 0; i-- { + if less(x[i], x[i-1]) { + return false + } + } + return true +} + +// BinarySearch searches for target in a sorted slice and returns the position +// where target is found, or the position where target would appear in the +// sort order; it also returns a bool saying whether the target is really found +// in the slice. The slice must be sorted in increasing order. +func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { + // Inlining is faster than calling BinarySearchFunc with a lambda. + n := len(x) + // Define x[-1] < target and x[n] >= target. + // Invariant: x[i-1] < target, x[j] >= target. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if x[h] < target { + i = h + 1 // preserves x[i-1] < target + } else { + j = h // preserves x[j] >= target + } + } + // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. + return i, i < n && x[i] == target +} + +// BinarySearchFunc works like BinarySearch, but uses a custom comparison +// function. The slice must be sorted in increasing order, where "increasing" is +// defined by cmp. cmp(a, b) is expected to return an integer comparing the two +// parameters: 0 if a == b, a negative number if a < b and a positive number if +// a > b. +func BinarySearchFunc[E any](x []E, target E, cmp func(E, E) int) (int, bool) { + n := len(x) + // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . + // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if cmp(x[h], target) < 0 { + i = h + 1 // preserves cmp(x[i - 1], target) < 0 + } else { + j = h // preserves cmp(x[j], target) >= 0 + } + } + // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. + return i, i < n && cmp(x[i], target) == 0 +} + +type sortedHint int // hint for pdqsort when choosing the pivot + +const ( + unknownHint sortedHint = iota + increasingHint + decreasingHint +) + +// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf +type xorshift uint64 + +func (r *xorshift) Next() uint64 { + *r ^= *r << 13 + *r ^= *r >> 17 + *r ^= *r << 5 + return uint64(*r) +} + +func nextPowerOfTwo(length int) uint { + return 1 << bits.Len(uint(length)) +} diff --git a/vendor/golang.org/x/exp/slices/zsortfunc.go b/vendor/golang.org/x/exp/slices/zsortfunc.go new file mode 100644 index 000000000..2a632476c --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortfunc.go @@ -0,0 +1,479 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +// insertionSortLessFunc sorts data[a:b] using insertion sort. +func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + for i := a + 1; i < b; i++ { + for j := i; j > a && less(data[j], data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownLessFunc implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && less(data[first+child], data[first+child+1]) { + child++ + } + if !less(data[first+root], data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownLessFunc(data, i, hi, first, less) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownLessFunc(data, lo, i, first, less) + } +} + +// pdqsortLessFunc sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortLessFunc(data, a, b, less) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortLessFunc(data, a, b, less) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsLessFunc(data, a, b, less) + limit-- + } + + pivot, hint := choosePivotLessFunc(data, a, b, less) + if hint == decreasingHint { + reverseRangeLessFunc(data, a, b, less) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortLessFunc(data, a, b, less) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !less(data[a-1], data[pivot]) { + mid := partitionEqualLessFunc(data, a, b, pivot, less) + a = mid + continue + } + + mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortLessFunc(data, a, mid, limit, less) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortLessFunc(data, mid+1, b, limit, less) + b = mid + } + } +} + +// partitionLessFunc does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !less(data[a], data[i]) { + i++ + } + for i <= j && less(data[a], data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !less(data[i], data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotLessFunc chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentLessFunc(data, i, &swaps, less) + j = medianAdjacentLessFunc(data, j, &swaps, less) + k = medianAdjacentLessFunc(data, k, &swaps, less) + } + // Find the median among i, j, k and stores it into j. + j = medianLessFunc(data, i, j, k, &swaps, less) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) { + if less(data[b], data[a]) { + *swaps++ + return b, a + } + return a, b +} + +// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int { + a, b = order2LessFunc(data, a, b, swaps, less) + b, c = order2LessFunc(data, b, c, swaps, less) + a, b = order2LessFunc(data, a, b, swaps, less) + return b +} + +// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int { + return medianLessFunc(data, a-1, a, a+1, swaps, less) +} + +func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortLessFunc(data, a, b, less) + a = b + b += blockSize + } + insertionSortLessFunc(data, a, n, less) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeLessFunc(data, a, a+blockSize, b, less) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeLessFunc(data, a, m, n, less) + } + blockSize *= 2 + } +} + +// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if less(data[h], data[a]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !less(data[m], data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !less(data[p-c], data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateLessFunc(data, start, m, end, less) + } + if a < start && start < mid { + symMergeLessFunc(data, a, start, mid, less) + } + if mid < end && end < b { + symMergeLessFunc(data, mid, end, b, less) + } +} + +// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeLessFunc(data, m-i, m, j, less) + i -= j + } else { + swapRangeLessFunc(data, m-i, m+j-i, i, less) + j -= i + } + } + // i == j + swapRangeLessFunc(data, m-i, m, i, less) +} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go new file mode 100644 index 000000000..efaa1c8b7 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortordered.go @@ -0,0 +1,481 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// insertionSortOrdered sorts data[a:b] using insertion sort. +func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && (data[j] < data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownOrdered implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && (data[first+child] < data[first+child+1]) { + child++ + } + if !(data[first+root] < data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownOrdered(data, i, hi, first) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownOrdered(data, lo, i, first) + } +} + +// pdqsortOrdered sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortOrdered(data, a, b) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortOrdered(data, a, b) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsOrdered(data, a, b) + limit-- + } + + pivot, hint := choosePivotOrdered(data, a, b) + if hint == decreasingHint { + reverseRangeOrdered(data, a, b) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortOrdered(data, a, b) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !(data[a-1] < data[pivot]) { + mid := partitionEqualOrdered(data, a, b, pivot) + a = mid + continue + } + + mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortOrdered(data, a, mid, limit) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortOrdered(data, mid+1, b, limit) + b = mid + } + } +} + +// partitionOrdered does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !(data[a] < data[i]) { + i++ + } + for i <= j && (data[a] < data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !(data[i] < data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsOrdered scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotOrdered chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentOrdered(data, i, &swaps) + j = medianAdjacentOrdered(data, j, &swaps) + k = medianAdjacentOrdered(data, k, &swaps) + } + // Find the median among i, j, k and stores it into j. + j = medianOrdered(data, i, j, k, &swaps) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { + if data[b] < data[a] { + *swaps++ + return b, a + } + return a, b +} + +// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { + a, b = order2Ordered(data, a, b, swaps) + b, c = order2Ordered(data, b, c, swaps) + a, b = order2Ordered(data, a, b, swaps) + return b +} + +// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { + return medianOrdered(data, a-1, a, a+1, swaps) +} + +func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableOrdered[E constraints.Ordered](data []E, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortOrdered(data, a, b) + a = b + b += blockSize + } + insertionSortOrdered(data, a, n) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeOrdered(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeOrdered(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if data[h] < data[a] { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !(data[m] < data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !(data[p-c] < data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateOrdered(data, start, m, end) + } + if a < start && start < mid { + symMergeOrdered(data, a, start, mid) + } + if mid < end && end < b { + symMergeOrdered(data, mid, end, b) + } +} + +// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeOrdered(data, m-i, m, j) + i -= j + } else { + swapRangeOrdered(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRangeOrdered(data, m-i, m, i) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 63863f75f..f53d922c3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -443,6 +443,10 @@ golang.org/x/crypto/nacl/secretbox golang.org/x/crypto/pkcs12 golang.org/x/crypto/pkcs12/internal/rc2 golang.org/x/crypto/salsa20/salsa +# golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 +## explicit; go 1.18 +golang.org/x/exp/constraints +golang.org/x/exp/slices # golang.org/x/net v0.1.0 ## explicit; go 1.17 golang.org/x/net/context @@ -1218,14 +1222,19 @@ k8s.io/utils/path k8s.io/utils/pointer k8s.io/utils/strings/slices k8s.io/utils/trace -# open-cluster-management.io/api v0.9.1-0.20230113024003-6529d52caeeb +# open-cluster-management.io/api v0.9.1-0.20230119012002-efddffc71682 ## explicit; go 1.19 open-cluster-management.io/api/client/cluster/clientset/versioned +open-cluster-management.io/api/client/cluster/clientset/versioned/fake open-cluster-management.io/api/client/cluster/clientset/versioned/scheme open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1 +open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1 +open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1 +open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2 +open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake open-cluster-management.io/api/client/cluster/informers/externalversions open-cluster-management.io/api/client/cluster/informers/externalversions/cluster open-cluster-management.io/api/client/cluster/informers/externalversions/cluster/v1 @@ -1256,6 +1265,7 @@ open-cluster-management.io/api/cluster/v1alpha1 open-cluster-management.io/api/cluster/v1beta1 open-cluster-management.io/api/cluster/v1beta2 open-cluster-management.io/api/feature +open-cluster-management.io/api/utils/work/v1/workapplier open-cluster-management.io/api/work/v1 open-cluster-management.io/api/work/v1alpha1 # sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/clientset_generated.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 000000000..e1309dca4 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,90 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" + clientset "open-cluster-management.io/api/client/cluster/clientset/versioned" + clusterv1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1" + fakeclusterv1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake" + clusterv1alpha1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1" + fakeclusterv1alpha1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake" + clusterv1beta1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1" + fakeclusterv1beta1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake" + clusterv1beta2 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2" + fakeclusterv1beta2 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// ClusterV1 retrieves the ClusterV1Client +func (c *Clientset) ClusterV1() clusterv1.ClusterV1Interface { + return &fakeclusterv1.FakeClusterV1{Fake: &c.Fake} +} + +// ClusterV1alpha1 retrieves the ClusterV1alpha1Client +func (c *Clientset) ClusterV1alpha1() clusterv1alpha1.ClusterV1alpha1Interface { + return &fakeclusterv1alpha1.FakeClusterV1alpha1{Fake: &c.Fake} +} + +// ClusterV1beta1 retrieves the ClusterV1beta1Client +func (c *Clientset) ClusterV1beta1() clusterv1beta1.ClusterV1beta1Interface { + return &fakeclusterv1beta1.FakeClusterV1beta1{Fake: &c.Fake} +} + +// ClusterV1beta2 retrieves the ClusterV1beta2Client +func (c *Clientset) ClusterV1beta2() clusterv1beta2.ClusterV1beta2Interface { + return &fakeclusterv1beta2.FakeClusterV1beta2{Fake: &c.Fake} +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/doc.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/doc.go new file mode 100644 index 000000000..3630ed1cd --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/doc.go @@ -0,0 +1,4 @@ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/register.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/register.go new file mode 100644 index 000000000..7c275db88 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/fake/register.go @@ -0,0 +1,46 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clusterv1 "open-cluster-management.io/api/cluster/v1" + clusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" + clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" + clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + clusterv1.AddToScheme, + clusterv1alpha1.AddToScheme, + clusterv1beta1.AddToScheme, + clusterv1beta2.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/doc.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/doc.go new file mode 100644 index 000000000..2b5ba4c8e --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/doc.go @@ -0,0 +1,4 @@ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_cluster_client.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_cluster_client.go new file mode 100644 index 000000000..3015e5cdf --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_cluster_client.go @@ -0,0 +1,24 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1" +) + +type FakeClusterV1 struct { + *testing.Fake +} + +func (c *FakeClusterV1) ManagedClusters() v1.ManagedClusterInterface { + return &FakeManagedClusters{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeClusterV1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_managedcluster.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_managedcluster.go new file mode 100644 index 000000000..fd5f6e8b7 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1/fake/fake_managedcluster.go @@ -0,0 +1,117 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + clusterv1 "open-cluster-management.io/api/cluster/v1" +) + +// FakeManagedClusters implements ManagedClusterInterface +type FakeManagedClusters struct { + Fake *FakeClusterV1 +} + +var managedclustersResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1", Resource: "managedclusters"} + +var managedclustersKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1", Kind: "ManagedCluster"} + +// Get takes name of the managedCluster, and returns the corresponding managedCluster object, and an error if there is any. +func (c *FakeManagedClusters) Get(ctx context.Context, name string, options v1.GetOptions) (result *clusterv1.ManagedCluster, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(managedclustersResource, name), &clusterv1.ManagedCluster{}) + if obj == nil { + return nil, err + } + return obj.(*clusterv1.ManagedCluster), err +} + +// List takes label and field selectors, and returns the list of ManagedClusters that match those selectors. +func (c *FakeManagedClusters) List(ctx context.Context, opts v1.ListOptions) (result *clusterv1.ManagedClusterList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(managedclustersResource, managedclustersKind, opts), &clusterv1.ManagedClusterList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &clusterv1.ManagedClusterList{ListMeta: obj.(*clusterv1.ManagedClusterList).ListMeta} + for _, item := range obj.(*clusterv1.ManagedClusterList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested managedClusters. +func (c *FakeManagedClusters) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(managedclustersResource, opts)) +} + +// Create takes the representation of a managedCluster and creates it. Returns the server's representation of the managedCluster, and an error, if there is any. +func (c *FakeManagedClusters) Create(ctx context.Context, managedCluster *clusterv1.ManagedCluster, opts v1.CreateOptions) (result *clusterv1.ManagedCluster, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(managedclustersResource, managedCluster), &clusterv1.ManagedCluster{}) + if obj == nil { + return nil, err + } + return obj.(*clusterv1.ManagedCluster), err +} + +// Update takes the representation of a managedCluster and updates it. Returns the server's representation of the managedCluster, and an error, if there is any. +func (c *FakeManagedClusters) Update(ctx context.Context, managedCluster *clusterv1.ManagedCluster, opts v1.UpdateOptions) (result *clusterv1.ManagedCluster, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(managedclustersResource, managedCluster), &clusterv1.ManagedCluster{}) + if obj == nil { + return nil, err + } + return obj.(*clusterv1.ManagedCluster), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeManagedClusters) UpdateStatus(ctx context.Context, managedCluster *clusterv1.ManagedCluster, opts v1.UpdateOptions) (*clusterv1.ManagedCluster, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(managedclustersResource, "status", managedCluster), &clusterv1.ManagedCluster{}) + if obj == nil { + return nil, err + } + return obj.(*clusterv1.ManagedCluster), err +} + +// Delete takes name of the managedCluster and deletes it. Returns an error if one occurs. +func (c *FakeManagedClusters) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(managedclustersResource, name, opts), &clusterv1.ManagedCluster{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeManagedClusters) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(managedclustersResource, listOpts) + + _, err := c.Fake.Invokes(action, &clusterv1.ManagedClusterList{}) + return err +} + +// Patch applies the patch and returns the patched managedCluster. +func (c *FakeManagedClusters) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *clusterv1.ManagedCluster, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(managedclustersResource, name, pt, data, subresources...), &clusterv1.ManagedCluster{}) + if obj == nil { + return nil, err + } + return obj.(*clusterv1.ManagedCluster), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/doc.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/doc.go new file mode 100644 index 000000000..2b5ba4c8e --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/doc.go @@ -0,0 +1,4 @@ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_addonplacementscore.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_addonplacementscore.go new file mode 100644 index 000000000..6534f5cb2 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_addonplacementscore.go @@ -0,0 +1,126 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" +) + +// FakeAddOnPlacementScores implements AddOnPlacementScoreInterface +type FakeAddOnPlacementScores struct { + Fake *FakeClusterV1alpha1 + ns string +} + +var addonplacementscoresResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1alpha1", Resource: "addonplacementscores"} + +var addonplacementscoresKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1alpha1", Kind: "AddOnPlacementScore"} + +// Get takes name of the addOnPlacementScore, and returns the corresponding addOnPlacementScore object, and an error if there is any. +func (c *FakeAddOnPlacementScores) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.AddOnPlacementScore, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(addonplacementscoresResource, c.ns, name), &v1alpha1.AddOnPlacementScore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.AddOnPlacementScore), err +} + +// List takes label and field selectors, and returns the list of AddOnPlacementScores that match those selectors. +func (c *FakeAddOnPlacementScores) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AddOnPlacementScoreList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(addonplacementscoresResource, addonplacementscoresKind, c.ns, opts), &v1alpha1.AddOnPlacementScoreList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.AddOnPlacementScoreList{ListMeta: obj.(*v1alpha1.AddOnPlacementScoreList).ListMeta} + for _, item := range obj.(*v1alpha1.AddOnPlacementScoreList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested addOnPlacementScores. +func (c *FakeAddOnPlacementScores) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(addonplacementscoresResource, c.ns, opts)) + +} + +// Create takes the representation of a addOnPlacementScore and creates it. Returns the server's representation of the addOnPlacementScore, and an error, if there is any. +func (c *FakeAddOnPlacementScores) Create(ctx context.Context, addOnPlacementScore *v1alpha1.AddOnPlacementScore, opts v1.CreateOptions) (result *v1alpha1.AddOnPlacementScore, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(addonplacementscoresResource, c.ns, addOnPlacementScore), &v1alpha1.AddOnPlacementScore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.AddOnPlacementScore), err +} + +// Update takes the representation of a addOnPlacementScore and updates it. Returns the server's representation of the addOnPlacementScore, and an error, if there is any. +func (c *FakeAddOnPlacementScores) Update(ctx context.Context, addOnPlacementScore *v1alpha1.AddOnPlacementScore, opts v1.UpdateOptions) (result *v1alpha1.AddOnPlacementScore, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(addonplacementscoresResource, c.ns, addOnPlacementScore), &v1alpha1.AddOnPlacementScore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.AddOnPlacementScore), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeAddOnPlacementScores) UpdateStatus(ctx context.Context, addOnPlacementScore *v1alpha1.AddOnPlacementScore, opts v1.UpdateOptions) (*v1alpha1.AddOnPlacementScore, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(addonplacementscoresResource, "status", c.ns, addOnPlacementScore), &v1alpha1.AddOnPlacementScore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.AddOnPlacementScore), err +} + +// Delete takes name of the addOnPlacementScore and deletes it. Returns an error if one occurs. +func (c *FakeAddOnPlacementScores) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(addonplacementscoresResource, c.ns, name, opts), &v1alpha1.AddOnPlacementScore{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeAddOnPlacementScores) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(addonplacementscoresResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.AddOnPlacementScoreList{}) + return err +} + +// Patch applies the patch and returns the patched addOnPlacementScore. +func (c *FakeAddOnPlacementScores) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AddOnPlacementScore, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(addonplacementscoresResource, c.ns, name, pt, data, subresources...), &v1alpha1.AddOnPlacementScore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.AddOnPlacementScore), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_cluster_client.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_cluster_client.go new file mode 100644 index 000000000..fa77aab53 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_cluster_client.go @@ -0,0 +1,28 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1alpha1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1" +) + +type FakeClusterV1alpha1 struct { + *testing.Fake +} + +func (c *FakeClusterV1alpha1) AddOnPlacementScores(namespace string) v1alpha1.AddOnPlacementScoreInterface { + return &FakeAddOnPlacementScores{c, namespace} +} + +func (c *FakeClusterV1alpha1) ClusterClaims() v1alpha1.ClusterClaimInterface { + return &FakeClusterClaims{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeClusterV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_clusterclaim.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_clusterclaim.go new file mode 100644 index 000000000..3f0af0809 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1alpha1/fake/fake_clusterclaim.go @@ -0,0 +1,106 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" +) + +// FakeClusterClaims implements ClusterClaimInterface +type FakeClusterClaims struct { + Fake *FakeClusterV1alpha1 +} + +var clusterclaimsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1alpha1", Resource: "clusterclaims"} + +var clusterclaimsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1alpha1", Kind: "ClusterClaim"} + +// Get takes name of the clusterClaim, and returns the corresponding clusterClaim object, and an error if there is any. +func (c *FakeClusterClaims) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ClusterClaim, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(clusterclaimsResource, name), &v1alpha1.ClusterClaim{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterClaim), err +} + +// List takes label and field selectors, and returns the list of ClusterClaims that match those selectors. +func (c *FakeClusterClaims) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ClusterClaimList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(clusterclaimsResource, clusterclaimsKind, opts), &v1alpha1.ClusterClaimList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ClusterClaimList{ListMeta: obj.(*v1alpha1.ClusterClaimList).ListMeta} + for _, item := range obj.(*v1alpha1.ClusterClaimList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested clusterClaims. +func (c *FakeClusterClaims) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(clusterclaimsResource, opts)) +} + +// Create takes the representation of a clusterClaim and creates it. Returns the server's representation of the clusterClaim, and an error, if there is any. +func (c *FakeClusterClaims) Create(ctx context.Context, clusterClaim *v1alpha1.ClusterClaim, opts v1.CreateOptions) (result *v1alpha1.ClusterClaim, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(clusterclaimsResource, clusterClaim), &v1alpha1.ClusterClaim{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterClaim), err +} + +// Update takes the representation of a clusterClaim and updates it. Returns the server's representation of the clusterClaim, and an error, if there is any. +func (c *FakeClusterClaims) Update(ctx context.Context, clusterClaim *v1alpha1.ClusterClaim, opts v1.UpdateOptions) (result *v1alpha1.ClusterClaim, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(clusterclaimsResource, clusterClaim), &v1alpha1.ClusterClaim{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterClaim), err +} + +// Delete takes name of the clusterClaim and deletes it. Returns an error if one occurs. +func (c *FakeClusterClaims) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(clusterclaimsResource, name, opts), &v1alpha1.ClusterClaim{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeClusterClaims) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(clusterclaimsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.ClusterClaimList{}) + return err +} + +// Patch applies the patch and returns the patched clusterClaim. +func (c *FakeClusterClaims) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterClaim, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(clusterclaimsResource, name, pt, data, subresources...), &v1alpha1.ClusterClaim{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterClaim), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/doc.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/doc.go new file mode 100644 index 000000000..2b5ba4c8e --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/doc.go @@ -0,0 +1,4 @@ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster_client.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster_client.go new file mode 100644 index 000000000..5bd66bb8e --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_cluster_client.go @@ -0,0 +1,36 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1beta1 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1" +) + +type FakeClusterV1beta1 struct { + *testing.Fake +} + +func (c *FakeClusterV1beta1) ManagedClusterSets() v1beta1.ManagedClusterSetInterface { + return &FakeManagedClusterSets{c} +} + +func (c *FakeClusterV1beta1) ManagedClusterSetBindings(namespace string) v1beta1.ManagedClusterSetBindingInterface { + return &FakeManagedClusterSetBindings{c, namespace} +} + +func (c *FakeClusterV1beta1) Placements(namespace string) v1beta1.PlacementInterface { + return &FakePlacements{c, namespace} +} + +func (c *FakeClusterV1beta1) PlacementDecisions(namespace string) v1beta1.PlacementDecisionInterface { + return &FakePlacementDecisions{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeClusterV1beta1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclusterset.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclusterset.go new file mode 100644 index 000000000..35ddbfed9 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclusterset.go @@ -0,0 +1,117 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "open-cluster-management.io/api/cluster/v1beta1" +) + +// FakeManagedClusterSets implements ManagedClusterSetInterface +type FakeManagedClusterSets struct { + Fake *FakeClusterV1beta1 +} + +var managedclustersetsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Resource: "managedclustersets"} + +var managedclustersetsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Kind: "ManagedClusterSet"} + +// Get takes name of the managedClusterSet, and returns the corresponding managedClusterSet object, and an error if there is any. +func (c *FakeManagedClusterSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(managedclustersetsResource, name), &v1beta1.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSet), err +} + +// List takes label and field selectors, and returns the list of ManagedClusterSets that match those selectors. +func (c *FakeManagedClusterSets) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ManagedClusterSetList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(managedclustersetsResource, managedclustersetsKind, opts), &v1beta1.ManagedClusterSetList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ManagedClusterSetList{ListMeta: obj.(*v1beta1.ManagedClusterSetList).ListMeta} + for _, item := range obj.(*v1beta1.ManagedClusterSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested managedClusterSets. +func (c *FakeManagedClusterSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(managedclustersetsResource, opts)) +} + +// Create takes the representation of a managedClusterSet and creates it. Returns the server's representation of the managedClusterSet, and an error, if there is any. +func (c *FakeManagedClusterSets) Create(ctx context.Context, managedClusterSet *v1beta1.ManagedClusterSet, opts v1.CreateOptions) (result *v1beta1.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(managedclustersetsResource, managedClusterSet), &v1beta1.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSet), err +} + +// Update takes the representation of a managedClusterSet and updates it. Returns the server's representation of the managedClusterSet, and an error, if there is any. +func (c *FakeManagedClusterSets) Update(ctx context.Context, managedClusterSet *v1beta1.ManagedClusterSet, opts v1.UpdateOptions) (result *v1beta1.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(managedclustersetsResource, managedClusterSet), &v1beta1.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSet), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeManagedClusterSets) UpdateStatus(ctx context.Context, managedClusterSet *v1beta1.ManagedClusterSet, opts v1.UpdateOptions) (*v1beta1.ManagedClusterSet, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(managedclustersetsResource, "status", managedClusterSet), &v1beta1.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSet), err +} + +// Delete takes name of the managedClusterSet and deletes it. Returns an error if one occurs. +func (c *FakeManagedClusterSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(managedclustersetsResource, name, opts), &v1beta1.ManagedClusterSet{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeManagedClusterSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(managedclustersetsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ManagedClusterSetList{}) + return err +} + +// Patch applies the patch and returns the patched managedClusterSet. +func (c *FakeManagedClusterSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(managedclustersetsResource, name, pt, data, subresources...), &v1beta1.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSet), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclustersetbinding.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclustersetbinding.go new file mode 100644 index 000000000..7b1cf5929 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_managedclustersetbinding.go @@ -0,0 +1,126 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "open-cluster-management.io/api/cluster/v1beta1" +) + +// FakeManagedClusterSetBindings implements ManagedClusterSetBindingInterface +type FakeManagedClusterSetBindings struct { + Fake *FakeClusterV1beta1 + ns string +} + +var managedclustersetbindingsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Resource: "managedclustersetbindings"} + +var managedclustersetbindingsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Kind: "ManagedClusterSetBinding"} + +// Get takes name of the managedClusterSetBinding, and returns the corresponding managedClusterSetBinding object, and an error if there is any. +func (c *FakeManagedClusterSetBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(managedclustersetbindingsResource, c.ns, name), &v1beta1.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSetBinding), err +} + +// List takes label and field selectors, and returns the list of ManagedClusterSetBindings that match those selectors. +func (c *FakeManagedClusterSetBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ManagedClusterSetBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(managedclustersetbindingsResource, managedclustersetbindingsKind, c.ns, opts), &v1beta1.ManagedClusterSetBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ManagedClusterSetBindingList{ListMeta: obj.(*v1beta1.ManagedClusterSetBindingList).ListMeta} + for _, item := range obj.(*v1beta1.ManagedClusterSetBindingList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested managedClusterSetBindings. +func (c *FakeManagedClusterSetBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(managedclustersetbindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a managedClusterSetBinding and creates it. Returns the server's representation of the managedClusterSetBinding, and an error, if there is any. +func (c *FakeManagedClusterSetBindings) Create(ctx context.Context, managedClusterSetBinding *v1beta1.ManagedClusterSetBinding, opts v1.CreateOptions) (result *v1beta1.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(managedclustersetbindingsResource, c.ns, managedClusterSetBinding), &v1beta1.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSetBinding), err +} + +// Update takes the representation of a managedClusterSetBinding and updates it. Returns the server's representation of the managedClusterSetBinding, and an error, if there is any. +func (c *FakeManagedClusterSetBindings) Update(ctx context.Context, managedClusterSetBinding *v1beta1.ManagedClusterSetBinding, opts v1.UpdateOptions) (result *v1beta1.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(managedclustersetbindingsResource, c.ns, managedClusterSetBinding), &v1beta1.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSetBinding), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeManagedClusterSetBindings) UpdateStatus(ctx context.Context, managedClusterSetBinding *v1beta1.ManagedClusterSetBinding, opts v1.UpdateOptions) (*v1beta1.ManagedClusterSetBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(managedclustersetbindingsResource, "status", c.ns, managedClusterSetBinding), &v1beta1.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSetBinding), err +} + +// Delete takes name of the managedClusterSetBinding and deletes it. Returns an error if one occurs. +func (c *FakeManagedClusterSetBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(managedclustersetbindingsResource, c.ns, name, opts), &v1beta1.ManagedClusterSetBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeManagedClusterSetBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(managedclustersetbindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ManagedClusterSetBindingList{}) + return err +} + +// Patch applies the patch and returns the patched managedClusterSetBinding. +func (c *FakeManagedClusterSetBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(managedclustersetbindingsResource, c.ns, name, pt, data, subresources...), &v1beta1.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ManagedClusterSetBinding), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placement.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placement.go new file mode 100644 index 000000000..1b8d26705 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placement.go @@ -0,0 +1,126 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "open-cluster-management.io/api/cluster/v1beta1" +) + +// FakePlacements implements PlacementInterface +type FakePlacements struct { + Fake *FakeClusterV1beta1 + ns string +} + +var placementsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Resource: "placements"} + +var placementsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Kind: "Placement"} + +// Get takes name of the placement, and returns the corresponding placement object, and an error if there is any. +func (c *FakePlacements) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Placement, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(placementsResource, c.ns, name), &v1beta1.Placement{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Placement), err +} + +// List takes label and field selectors, and returns the list of Placements that match those selectors. +func (c *FakePlacements) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.PlacementList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(placementsResource, placementsKind, c.ns, opts), &v1beta1.PlacementList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.PlacementList{ListMeta: obj.(*v1beta1.PlacementList).ListMeta} + for _, item := range obj.(*v1beta1.PlacementList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested placements. +func (c *FakePlacements) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(placementsResource, c.ns, opts)) + +} + +// Create takes the representation of a placement and creates it. Returns the server's representation of the placement, and an error, if there is any. +func (c *FakePlacements) Create(ctx context.Context, placement *v1beta1.Placement, opts v1.CreateOptions) (result *v1beta1.Placement, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(placementsResource, c.ns, placement), &v1beta1.Placement{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Placement), err +} + +// Update takes the representation of a placement and updates it. Returns the server's representation of the placement, and an error, if there is any. +func (c *FakePlacements) Update(ctx context.Context, placement *v1beta1.Placement, opts v1.UpdateOptions) (result *v1beta1.Placement, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(placementsResource, c.ns, placement), &v1beta1.Placement{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Placement), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePlacements) UpdateStatus(ctx context.Context, placement *v1beta1.Placement, opts v1.UpdateOptions) (*v1beta1.Placement, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(placementsResource, "status", c.ns, placement), &v1beta1.Placement{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Placement), err +} + +// Delete takes name of the placement and deletes it. Returns an error if one occurs. +func (c *FakePlacements) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(placementsResource, c.ns, name, opts), &v1beta1.Placement{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePlacements) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(placementsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.PlacementList{}) + return err +} + +// Patch applies the patch and returns the patched placement. +func (c *FakePlacements) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Placement, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(placementsResource, c.ns, name, pt, data, subresources...), &v1beta1.Placement{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Placement), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placementdecision.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placementdecision.go new file mode 100644 index 000000000..b87b0e1b1 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta1/fake/fake_placementdecision.go @@ -0,0 +1,126 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "open-cluster-management.io/api/cluster/v1beta1" +) + +// FakePlacementDecisions implements PlacementDecisionInterface +type FakePlacementDecisions struct { + Fake *FakeClusterV1beta1 + ns string +} + +var placementdecisionsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Resource: "placementdecisions"} + +var placementdecisionsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta1", Kind: "PlacementDecision"} + +// Get takes name of the placementDecision, and returns the corresponding placementDecision object, and an error if there is any. +func (c *FakePlacementDecisions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.PlacementDecision, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(placementdecisionsResource, c.ns, name), &v1beta1.PlacementDecision{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PlacementDecision), err +} + +// List takes label and field selectors, and returns the list of PlacementDecisions that match those selectors. +func (c *FakePlacementDecisions) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.PlacementDecisionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(placementdecisionsResource, placementdecisionsKind, c.ns, opts), &v1beta1.PlacementDecisionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.PlacementDecisionList{ListMeta: obj.(*v1beta1.PlacementDecisionList).ListMeta} + for _, item := range obj.(*v1beta1.PlacementDecisionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested placementDecisions. +func (c *FakePlacementDecisions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(placementdecisionsResource, c.ns, opts)) + +} + +// Create takes the representation of a placementDecision and creates it. Returns the server's representation of the placementDecision, and an error, if there is any. +func (c *FakePlacementDecisions) Create(ctx context.Context, placementDecision *v1beta1.PlacementDecision, opts v1.CreateOptions) (result *v1beta1.PlacementDecision, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(placementdecisionsResource, c.ns, placementDecision), &v1beta1.PlacementDecision{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PlacementDecision), err +} + +// Update takes the representation of a placementDecision and updates it. Returns the server's representation of the placementDecision, and an error, if there is any. +func (c *FakePlacementDecisions) Update(ctx context.Context, placementDecision *v1beta1.PlacementDecision, opts v1.UpdateOptions) (result *v1beta1.PlacementDecision, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(placementdecisionsResource, c.ns, placementDecision), &v1beta1.PlacementDecision{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PlacementDecision), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePlacementDecisions) UpdateStatus(ctx context.Context, placementDecision *v1beta1.PlacementDecision, opts v1.UpdateOptions) (*v1beta1.PlacementDecision, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(placementdecisionsResource, "status", c.ns, placementDecision), &v1beta1.PlacementDecision{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PlacementDecision), err +} + +// Delete takes name of the placementDecision and deletes it. Returns an error if one occurs. +func (c *FakePlacementDecisions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(placementdecisionsResource, c.ns, name, opts), &v1beta1.PlacementDecision{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePlacementDecisions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(placementdecisionsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.PlacementDecisionList{}) + return err +} + +// Patch applies the patch and returns the patched placementDecision. +func (c *FakePlacementDecisions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.PlacementDecision, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(placementdecisionsResource, c.ns, name, pt, data, subresources...), &v1beta1.PlacementDecision{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PlacementDecision), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/doc.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/doc.go new file mode 100644 index 000000000..2b5ba4c8e --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/doc.go @@ -0,0 +1,4 @@ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_cluster_client.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_cluster_client.go new file mode 100644 index 000000000..b123e4b73 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_cluster_client.go @@ -0,0 +1,28 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1beta2 "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2" +) + +type FakeClusterV1beta2 struct { + *testing.Fake +} + +func (c *FakeClusterV1beta2) ManagedClusterSets() v1beta2.ManagedClusterSetInterface { + return &FakeManagedClusterSets{c} +} + +func (c *FakeClusterV1beta2) ManagedClusterSetBindings(namespace string) v1beta2.ManagedClusterSetBindingInterface { + return &FakeManagedClusterSetBindings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeClusterV1beta2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclusterset.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclusterset.go new file mode 100644 index 000000000..94fed9547 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclusterset.go @@ -0,0 +1,117 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta2 "open-cluster-management.io/api/cluster/v1beta2" +) + +// FakeManagedClusterSets implements ManagedClusterSetInterface +type FakeManagedClusterSets struct { + Fake *FakeClusterV1beta2 +} + +var managedclustersetsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta2", Resource: "managedclustersets"} + +var managedclustersetsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta2", Kind: "ManagedClusterSet"} + +// Get takes name of the managedClusterSet, and returns the corresponding managedClusterSet object, and an error if there is any. +func (c *FakeManagedClusterSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(managedclustersetsResource, name), &v1beta2.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSet), err +} + +// List takes label and field selectors, and returns the list of ManagedClusterSets that match those selectors. +func (c *FakeManagedClusterSets) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ManagedClusterSetList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(managedclustersetsResource, managedclustersetsKind, opts), &v1beta2.ManagedClusterSetList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta2.ManagedClusterSetList{ListMeta: obj.(*v1beta2.ManagedClusterSetList).ListMeta} + for _, item := range obj.(*v1beta2.ManagedClusterSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested managedClusterSets. +func (c *FakeManagedClusterSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(managedclustersetsResource, opts)) +} + +// Create takes the representation of a managedClusterSet and creates it. Returns the server's representation of the managedClusterSet, and an error, if there is any. +func (c *FakeManagedClusterSets) Create(ctx context.Context, managedClusterSet *v1beta2.ManagedClusterSet, opts v1.CreateOptions) (result *v1beta2.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(managedclustersetsResource, managedClusterSet), &v1beta2.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSet), err +} + +// Update takes the representation of a managedClusterSet and updates it. Returns the server's representation of the managedClusterSet, and an error, if there is any. +func (c *FakeManagedClusterSets) Update(ctx context.Context, managedClusterSet *v1beta2.ManagedClusterSet, opts v1.UpdateOptions) (result *v1beta2.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(managedclustersetsResource, managedClusterSet), &v1beta2.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSet), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeManagedClusterSets) UpdateStatus(ctx context.Context, managedClusterSet *v1beta2.ManagedClusterSet, opts v1.UpdateOptions) (*v1beta2.ManagedClusterSet, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(managedclustersetsResource, "status", managedClusterSet), &v1beta2.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSet), err +} + +// Delete takes name of the managedClusterSet and deletes it. Returns an error if one occurs. +func (c *FakeManagedClusterSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(managedclustersetsResource, name, opts), &v1beta2.ManagedClusterSet{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeManagedClusterSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(managedclustersetsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta2.ManagedClusterSetList{}) + return err +} + +// Patch applies the patch and returns the patched managedClusterSet. +func (c *FakeManagedClusterSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.ManagedClusterSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(managedclustersetsResource, name, pt, data, subresources...), &v1beta2.ManagedClusterSet{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSet), err +} diff --git a/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclustersetbinding.go b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclustersetbinding.go new file mode 100644 index 000000000..920369273 --- /dev/null +++ b/vendor/open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1beta2/fake/fake_managedclustersetbinding.go @@ -0,0 +1,126 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta2 "open-cluster-management.io/api/cluster/v1beta2" +) + +// FakeManagedClusterSetBindings implements ManagedClusterSetBindingInterface +type FakeManagedClusterSetBindings struct { + Fake *FakeClusterV1beta2 + ns string +} + +var managedclustersetbindingsResource = schema.GroupVersionResource{Group: "cluster.open-cluster-management.io", Version: "v1beta2", Resource: "managedclustersetbindings"} + +var managedclustersetbindingsKind = schema.GroupVersionKind{Group: "cluster.open-cluster-management.io", Version: "v1beta2", Kind: "ManagedClusterSetBinding"} + +// Get takes name of the managedClusterSetBinding, and returns the corresponding managedClusterSetBinding object, and an error if there is any. +func (c *FakeManagedClusterSetBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(managedclustersetbindingsResource, c.ns, name), &v1beta2.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSetBinding), err +} + +// List takes label and field selectors, and returns the list of ManagedClusterSetBindings that match those selectors. +func (c *FakeManagedClusterSetBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ManagedClusterSetBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(managedclustersetbindingsResource, managedclustersetbindingsKind, c.ns, opts), &v1beta2.ManagedClusterSetBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta2.ManagedClusterSetBindingList{ListMeta: obj.(*v1beta2.ManagedClusterSetBindingList).ListMeta} + for _, item := range obj.(*v1beta2.ManagedClusterSetBindingList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested managedClusterSetBindings. +func (c *FakeManagedClusterSetBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(managedclustersetbindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a managedClusterSetBinding and creates it. Returns the server's representation of the managedClusterSetBinding, and an error, if there is any. +func (c *FakeManagedClusterSetBindings) Create(ctx context.Context, managedClusterSetBinding *v1beta2.ManagedClusterSetBinding, opts v1.CreateOptions) (result *v1beta2.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(managedclustersetbindingsResource, c.ns, managedClusterSetBinding), &v1beta2.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSetBinding), err +} + +// Update takes the representation of a managedClusterSetBinding and updates it. Returns the server's representation of the managedClusterSetBinding, and an error, if there is any. +func (c *FakeManagedClusterSetBindings) Update(ctx context.Context, managedClusterSetBinding *v1beta2.ManagedClusterSetBinding, opts v1.UpdateOptions) (result *v1beta2.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(managedclustersetbindingsResource, c.ns, managedClusterSetBinding), &v1beta2.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSetBinding), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeManagedClusterSetBindings) UpdateStatus(ctx context.Context, managedClusterSetBinding *v1beta2.ManagedClusterSetBinding, opts v1.UpdateOptions) (*v1beta2.ManagedClusterSetBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(managedclustersetbindingsResource, "status", c.ns, managedClusterSetBinding), &v1beta2.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSetBinding), err +} + +// Delete takes name of the managedClusterSetBinding and deletes it. Returns an error if one occurs. +func (c *FakeManagedClusterSetBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(managedclustersetbindingsResource, c.ns, name, opts), &v1beta2.ManagedClusterSetBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeManagedClusterSetBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(managedclustersetbindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta2.ManagedClusterSetBindingList{}) + return err +} + +// Patch applies the patch and returns the patched managedClusterSetBinding. +func (c *FakeManagedClusterSetBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.ManagedClusterSetBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(managedclustersetbindingsResource, c.ns, name, pt, data, subresources...), &v1beta2.ManagedClusterSetBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.ManagedClusterSetBinding), err +} diff --git a/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workapplier.go b/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workapplier.go new file mode 100644 index 000000000..a15d2016c --- /dev/null +++ b/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workapplier.go @@ -0,0 +1,138 @@ +package workapplier + +import ( + "context" + "encoding/json" + "fmt" + + jsonpatch "github.com/evanphx/json-patch" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" + workv1client "open-cluster-management.io/api/client/work/clientset/versioned" + worklister "open-cluster-management.io/api/client/work/listers/work/v1" + workapiv1 "open-cluster-management.io/api/work/v1" +) + +type WorkApplier struct { + cache *workCache + getWork func(ctx context.Context, namespace, name string) (*workapiv1.ManifestWork, error) + deleteWork func(ctx context.Context, namespace, name string) error + patchWork func(ctx context.Context, namespace, name string, pt types.PatchType, data []byte) (*workapiv1.ManifestWork, error) + createWork func(ctx context.Context, work *workapiv1.ManifestWork) (*workapiv1.ManifestWork, error) +} + +func NewWorkApplierWithTypedClient(workClient workv1client.Interface, + workLister worklister.ManifestWorkLister) *WorkApplier { + return &WorkApplier{ + cache: newWorkCache(), + getWork: func(ctx context.Context, namespace, name string) (*workapiv1.ManifestWork, error) { + return workLister.ManifestWorks(namespace).Get(name) + }, + deleteWork: func(ctx context.Context, namespace, name string) error { + return workClient.WorkV1().ManifestWorks(namespace).Delete(ctx, name, metav1.DeleteOptions{}) + }, + createWork: func(ctx context.Context, work *workapiv1.ManifestWork) (*workapiv1.ManifestWork, error) { + return workClient.WorkV1().ManifestWorks(work.Namespace).Create(ctx, work, metav1.CreateOptions{}) + }, + patchWork: func(ctx context.Context, namespace, name string, pt types.PatchType, data []byte) (*workapiv1.ManifestWork, error) { + return workClient.WorkV1().ManifestWorks(namespace).Patch(ctx, name, pt, data, metav1.PatchOptions{}) + }, + } +} + +func (w *WorkApplier) Apply(ctx context.Context, work *workapiv1.ManifestWork) (*workapiv1.ManifestWork, error) { + existingWork, err := w.getWork(ctx, work.Namespace, work.Name) + existingWork = existingWork.DeepCopy() + if err != nil { + if errors.IsNotFound(err) { + existingWork, err = w.createWork(ctx, work) + if err == nil { + w.cache.updateCache(work, existingWork) + return existingWork, nil + } + return nil, err + } + return nil, err + } + + if w.cache.safeToSkipApply(work, existingWork) { + return existingWork, nil + } + + if ManifestWorkSpecEqual(work.Spec, existingWork.Spec) { + return existingWork, nil + } + + oldData, err := json.Marshal(&workapiv1.ManifestWork{ + Spec: existingWork.Spec, + }) + if err != nil { + return existingWork, err + } + + newData, err := json.Marshal(&workapiv1.ManifestWork{ + ObjectMeta: metav1.ObjectMeta{ + UID: existingWork.UID, + ResourceVersion: existingWork.ResourceVersion, + }, + Spec: work.Spec, + }) + if err != nil { + return existingWork, err + } + + patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) + if err != nil { + return existingWork, fmt.Errorf("failed to create patch for addon %s: %w", existingWork.Name, err) + } + + klog.V(2).Infof("Patching work %s/%s with %s", existingWork.Namespace, existingWork.Name, string(patchBytes)) + updated, err := w.patchWork(ctx, existingWork.Namespace, existingWork.Name, types.MergePatchType, patchBytes) + if err == nil { + w.cache.updateCache(work, existingWork) + return updated, nil + } + return nil, err +} + +func (w *WorkApplier) Delete(ctx context.Context, namespace, name string) error { + err := w.deleteWork(ctx, namespace, name) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + + w.cache.removeCache(name, namespace) + return nil +} + +func manifestsEqual(new, old []workapiv1.Manifest) bool { + if len(new) != len(old) { + return false + } + + for i := range new { + if !equality.Semantic.DeepEqual(new[i].Raw, old[i].Raw) { + return false + } + } + return true +} + +func ManifestWorkSpecEqual(new, old workapiv1.ManifestWorkSpec) bool { + if !manifestsEqual(new.Workload.Manifests, old.Workload.Manifests) { + return false + } + if !equality.Semantic.DeepEqual(new.ManifestConfigs, old.ManifestConfigs) { + return false + } + if !equality.Semantic.DeepEqual(new.DeleteOption, old.DeleteOption) { + return false + } + return true +} diff --git a/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workcache.go b/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workcache.go new file mode 100644 index 000000000..f362a16c2 --- /dev/null +++ b/vendor/open-cluster-management.io/api/utils/work/v1/workapplier/workcache.go @@ -0,0 +1,102 @@ +package workapplier + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "io" + "sync" + + "k8s.io/klog/v2" + workapiv1 "open-cluster-management.io/api/work/v1" +) + +type workKey struct { + cluster string + name string +} + +type cachedResource struct { + resourceHash string + generation int64 +} + +type workCache struct { + mutex sync.RWMutex + cache map[workKey]cachedResource +} + +func newWorkCache() *workCache { + return &workCache{ + cache: map[workKey]cachedResource{}, + } +} + +func (w *workCache) updateCache(required, existing *workapiv1.ManifestWork) { + if required == nil || existing == nil { + return + } + w.mutex.Lock() + defer w.mutex.Unlock() + + key := workKey{ + cluster: required.Namespace, + name: required.Name, + } + + value := cachedResource{ + resourceHash: hashOfResourceStruct(required), + generation: existing.Generation, + } + + w.cache[key] = value +} + +func (w *workCache) removeCache(name, cluster string) { + key := workKey{ + cluster: cluster, + name: name, + } + + w.mutex.Lock() + defer w.mutex.Unlock() + delete(w.cache, key) +} + +func (w *workCache) safeToSkipApply(required, existing *workapiv1.ManifestWork) bool { + if required == nil || existing == nil { + return false + } + + cacheKey := workKey{ + cluster: required.Namespace, + name: required.Name, + } + + resourceHash := hashOfResourceStruct(required) + + generation := existing.Generation + w.mutex.RLock() + defer w.mutex.RUnlock() + var generationMatch, hashMatch bool + if cached, exists := w.cache[cacheKey]; exists { + generationMatch = cached.generation == generation + hashMatch = cached.resourceHash == resourceHash + if generationMatch && hashMatch { + klog.V(4).Infof("found matching generation & manifest hash") + return true + } + } + + return false +} + +func hashOfResourceStruct(o interface{}) string { + oString, _ := json.Marshal(o) + h := md5.New() + if _, err := io.WriteString(h, string(oString)); err != nil { + klog.Errorf("failed to hash object: %v", err) + } + rval := fmt.Sprintf("%x", h.Sum(nil)) + return rval +}