Add create,update and delete placeManifestWork (#177)

Signed-off-by: melserngawy <melserng@redhat.com>
This commit is contained in:
Mohamed ElSerngawy
2023-02-06 20:23:44 -05:00
committed by GitHub
parent d8f960f9e3
commit 8dd52acfae
44 changed files with 4146 additions and 44 deletions
+2 -1
View File
@@ -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
)
+4 -2
View File
@@ -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=
+14 -22
View File
@@ -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()
@@ -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")
}
}
@@ -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()},
},
}
@@ -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])
}
}
@@ -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
}
@@ -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)
}
}
@@ -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)
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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
}
@@ -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)
}
}
+79
View File
@@ -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
}
+27
View File
@@ -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.
+22
View File
@@ -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.
+50
View File
@@ -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
}
+258
View File
@@ -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)]
}
+126
View File
@@ -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))
}
+479
View File
@@ -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 and data[j]>=p for i<newpivot and j>newpivot.
// 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)
}
+481
View File
@@ -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 and data[j]>=p for i<newpivot and j>newpivot.
// 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)
}
+11 -1
View File
@@ -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
@@ -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}
}
@@ -0,0 +1,4 @@
// Code generated by client-gen. DO NOT EDIT.
// This package has the automatically generated fake clientset.
package fake
@@ -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))
}
@@ -0,0 +1,4 @@
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -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
}
@@ -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
}
@@ -0,0 +1,4 @@
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -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
}
@@ -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
}
@@ -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
}
@@ -0,0 +1,4 @@
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -0,0 +1,4 @@
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}