Merge pull request #2263 from zirain/namespace-cop
support ClusterOverridePolicy in namespaces_sync_controller
This commit is contained in:
commit
b860ba56c7
|
|
@ -367,6 +367,7 @@ func startNamespaceController(ctx controllerscontext.Context) (enabled bool, err
|
|||
Client: ctx.Mgr.GetClient(),
|
||||
EventRecorder: ctx.Mgr.GetEventRecorderFor(namespace.ControllerName),
|
||||
SkippedPropagatingNamespaces: skippedPropagatingNamespaces,
|
||||
OverrideManager: ctx.OverrideManager,
|
||||
}
|
||||
if err := namespaceSyncController.SetupWithManager(ctx.Mgr); err != nil {
|
||||
return false, err
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func ensureWork(
|
|||
workLabel := mergeLabel(clonedWorkload, workNamespace, binding, scope)
|
||||
|
||||
annotations := mergeAnnotations(clonedWorkload, binding, scope)
|
||||
annotations, err = recordAppliedOverrides(cops, ops, annotations)
|
||||
annotations, err = RecordAppliedOverrides(cops, ops, annotations)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to record appliedOverrides, Error: %v", err)
|
||||
return err
|
||||
|
|
@ -209,7 +209,8 @@ func mergeAnnotations(workload *unstructured.Unstructured, binding metav1.Object
|
|||
return annotations
|
||||
}
|
||||
|
||||
func recordAppliedOverrides(cops *overridemanager.AppliedOverrides, ops *overridemanager.AppliedOverrides,
|
||||
// RecordAppliedOverrides record applied (cluster) overrides to annotations
|
||||
func RecordAppliedOverrides(cops *overridemanager.AppliedOverrides, ops *overridemanager.AppliedOverrides,
|
||||
annotations map[string]string) (map[string]string, error) {
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/klog/v2"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
|
|
@ -19,10 +20,13 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/controllers/binding"
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/names"
|
||||
"github.com/karmada-io/karmada/pkg/util/overridemanager"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -35,6 +39,7 @@ type Controller struct {
|
|||
client.Client // used to operate Work resources.
|
||||
EventRecorder record.EventRecorder
|
||||
SkippedPropagatingNamespaces map[string]struct{}
|
||||
OverrideManager overridemanager.OverrideManager
|
||||
}
|
||||
|
||||
// Reconcile performs a full reconciliation for the object referred to by the Request.
|
||||
|
|
@ -96,6 +101,21 @@ func (c *Controller) buildWorks(namespace *corev1.Namespace, clusters []clusterv
|
|||
}
|
||||
|
||||
for _, cluster := range clusters {
|
||||
clonedNamespaced := namespaceObj.DeepCopy()
|
||||
|
||||
// namespace only care about ClusterOverridePolicy
|
||||
cops, _, err := c.OverrideManager.ApplyOverridePolicies(clonedNamespaced, cluster.Name)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to apply overrides for %s/%s/%s, err is: %v", clonedNamespaced.GetKind(), clonedNamespaced.GetNamespace(), clonedNamespaced.GetName(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
annotations, err := binding.RecordAppliedOverrides(cops, nil, nil)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to record appliedOverrides, Error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
workNamespace, err := names.GenerateExecutionSpaceName(cluster.Name)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to generate execution space name for member cluster %s, err is %v", cluster.Name, err)
|
||||
|
|
@ -110,12 +130,13 @@ func (c *Controller) buildWorks(namespace *corev1.Namespace, clusters []clusterv
|
|||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(namespace, namespace.GroupVersionKind()),
|
||||
},
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
util.MergeLabel(namespaceObj, workv1alpha1.WorkNamespaceLabel, workNamespace)
|
||||
util.MergeLabel(namespaceObj, workv1alpha1.WorkNameLabel, workName)
|
||||
util.MergeLabel(clonedNamespaced, workv1alpha1.WorkNamespaceLabel, workNamespace)
|
||||
util.MergeLabel(clonedNamespaced, workv1alpha1.WorkNameLabel, workName)
|
||||
|
||||
if err = helper.CreateOrUpdateWork(c.Client, objectMeta, namespaceObj); err != nil {
|
||||
if err = helper.CreateOrUpdateWork(c.Client, objectMeta, clonedNamespaced); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +145,7 @@ func (c *Controller) buildWorks(namespace *corev1.Namespace, clusters []clusterv
|
|||
|
||||
// SetupWithManager creates a controller and register to controller manager.
|
||||
func (c *Controller) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
namespaceFn := handler.MapFunc(
|
||||
clusterNamespaceFn := handler.MapFunc(
|
||||
func(a client.Object) []reconcile.Request {
|
||||
var requests []reconcile.Request
|
||||
namespaceList := &corev1.NamespaceList{}
|
||||
|
|
@ -141,7 +162,7 @@ func (c *Controller) SetupWithManager(mgr controllerruntime.Manager) error {
|
|||
return requests
|
||||
})
|
||||
|
||||
predicate := builder.WithPredicates(predicate.Funcs{
|
||||
clusterPredicate := builder.WithPredicates(predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
return true
|
||||
},
|
||||
|
|
@ -156,7 +177,76 @@ func (c *Controller) SetupWithManager(mgr controllerruntime.Manager) error {
|
|||
},
|
||||
})
|
||||
|
||||
clusterOverridePolicyNamespaceFn := handler.MapFunc(
|
||||
func(obj client.Object) []reconcile.Request {
|
||||
var requests []reconcile.Request
|
||||
cop, ok := obj.(*policyv1alpha1.ClusterOverridePolicy)
|
||||
if !ok {
|
||||
return requests
|
||||
}
|
||||
|
||||
selectedNamespaces := sets.NewString()
|
||||
containsAllNamespace := false
|
||||
for _, rs := range cop.Spec.ResourceSelectors {
|
||||
if rs.APIVersion != "v1" || rs.Kind != "Namespace" {
|
||||
continue
|
||||
}
|
||||
|
||||
if rs.Name == "" {
|
||||
containsAllNamespace = true
|
||||
break
|
||||
}
|
||||
|
||||
selectedNamespaces.Insert(rs.Name)
|
||||
}
|
||||
|
||||
if containsAllNamespace {
|
||||
namespaceList := &corev1.NamespaceList{}
|
||||
if err := c.Client.List(context.TODO(), namespaceList); err != nil {
|
||||
klog.Errorf("Failed to list namespace, error: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, namespace := range namespaceList.Items {
|
||||
requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{
|
||||
Name: namespace.Name,
|
||||
}})
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
for _, ns := range selectedNamespaces.UnsortedList() {
|
||||
requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{
|
||||
Name: ns,
|
||||
}})
|
||||
}
|
||||
|
||||
return requests
|
||||
})
|
||||
|
||||
clusterOverridePolicyPredicate := builder.WithPredicates(predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
return true
|
||||
},
|
||||
UpdateFunc: func(e event.UpdateEvent) bool {
|
||||
return true
|
||||
},
|
||||
DeleteFunc: func(event.DeleteEvent) bool {
|
||||
return true
|
||||
},
|
||||
GenericFunc: func(event.GenericEvent) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Namespace{}).Watches(&source.Kind{Type: &clusterv1alpha1.Cluster{}}, handler.EnqueueRequestsFromMapFunc(namespaceFn),
|
||||
predicate).Complete(c)
|
||||
For(&corev1.Namespace{}).
|
||||
Watches(&source.Kind{Type: &clusterv1alpha1.Cluster{}},
|
||||
handler.EnqueueRequestsFromMapFunc(clusterNamespaceFn),
|
||||
clusterPredicate).
|
||||
Watches(&source.Kind{Type: &policyv1alpha1.ClusterOverridePolicy{}},
|
||||
handler.EnqueueRequestsFromMapFunc(clusterOverridePolicyNamespaceFn),
|
||||
clusterOverridePolicyPredicate).
|
||||
Complete(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
"github.com/karmada-io/karmada/test/e2e/framework"
|
||||
testhelper "github.com/karmada-io/karmada/test/helper"
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("[BasicClusterOverridePolicy] basic cluster override policy testing", func() {
|
||||
ginkgo.Context("Namespace propagation testing", func() {
|
||||
var (
|
||||
randNamespace string
|
||||
ns *corev1.Namespace
|
||||
namespaceCop *policyv1alpha1.ClusterOverridePolicy
|
||||
|
||||
customLabelKey = "hello"
|
||||
customLabelVal = "world"
|
||||
plaintextOverriderValue = fmt.Sprintf("{ \"%s\": \"%s\"}", customLabelKey, customLabelVal)
|
||||
)
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
randNamespace = "cop-test-ns-" + rand.String(RandomStrLength)
|
||||
ns = testhelper.NewNamespace(randNamespace)
|
||||
namespaceCop = testhelper.NewClusterOverridePolicyByOverrideRules(ns.Name,
|
||||
[]policyv1alpha1.ResourceSelector{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Namespace",
|
||||
Name: ns.Name,
|
||||
},
|
||||
},
|
||||
[]policyv1alpha1.RuleWithCluster{
|
||||
{
|
||||
TargetCluster: &policyv1alpha1.ClusterAffinity{
|
||||
ClusterNames: framework.ClusterNames(),
|
||||
},
|
||||
Overriders: policyv1alpha1.Overriders{
|
||||
Plaintext: []policyv1alpha1.PlaintextOverrider{
|
||||
{
|
||||
Path: "/metadata/labels",
|
||||
Operator: policyv1alpha1.OverriderOpAdd,
|
||||
Value: apiextensionsv1.JSON{Raw: []byte(plaintextOverriderValue)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
framework.CreateClusterOverridePolicy(karmadaClient, namespaceCop)
|
||||
framework.CreateNamespace(kubeClient, ns)
|
||||
ginkgo.DeferCleanup(func() {
|
||||
framework.RemoveClusterOverridePolicy(karmadaClient, namespaceCop.Name)
|
||||
framework.RemoveNamespace(kubeClient, ns.Name)
|
||||
framework.WaitNamespaceDisappearOnClusters(framework.ClusterNames(), ns.Name)
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.It("Namespace testing", func() {
|
||||
ginkgo.By(fmt.Sprintf("Check if namespace(%s) present on member clusters", ns.Name), func() {
|
||||
for _, clusterName := range framework.ClusterNames() {
|
||||
clusterClient := framework.GetClusterClient(clusterName)
|
||||
gomega.Expect(clusterClient).ShouldNot(gomega.BeNil())
|
||||
|
||||
klog.Infof("Waiting for namespace present on cluster(%s)", clusterName)
|
||||
gomega.Eventually(func(g gomega.Gomega) (bool, error) {
|
||||
clusterNs, err := clusterClient.CoreV1().Namespaces().Get(context.TODO(), ns.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
v, ok := clusterNs.Labels[customLabelKey]
|
||||
if ok && v == customLabelVal {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}, pollTimeout, pollInterval).Should(gomega.Equal(true))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package framework
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
karmada "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
|
||||
)
|
||||
|
||||
// CreateClusterOverridePolicy create ClusterOverridePolicy with karmada client.
|
||||
func CreateClusterOverridePolicy(client karmada.Interface, policy *policyv1alpha1.ClusterOverridePolicy) {
|
||||
ginkgo.By(fmt.Sprintf("Creating ClusterOverridePolicy(%s)", policy.Name), func() {
|
||||
_, err := client.PolicyV1alpha1().ClusterOverridePolicies().Create(context.TODO(), policy, metav1.CreateOptions{})
|
||||
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveClusterOverridePolicy delete ClusterOverridePolicy with karmada client.
|
||||
func RemoveClusterOverridePolicy(client karmada.Interface, name string) {
|
||||
ginkgo.By(fmt.Sprintf("Removing ClusterOverridePolicy(%s)", name), func() {
|
||||
err := client.PolicyV1alpha1().ClusterOverridePolicies().Delete(context.TODO(), name, metav1.DeleteOptions{})
|
||||
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
|
@ -64,6 +64,19 @@ func NewOverridePolicyByOverrideRules(namespace, policyName string, rsSelectors
|
|||
}
|
||||
}
|
||||
|
||||
// NewClusterOverridePolicyByOverrideRules will build a ClusterOverridePolicy object by OverrideRules
|
||||
func NewClusterOverridePolicyByOverrideRules(policyName string, rsSelectors []policyv1alpha1.ResourceSelector, overrideRules []policyv1alpha1.RuleWithCluster) *policyv1alpha1.ClusterOverridePolicy {
|
||||
return &policyv1alpha1.ClusterOverridePolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: policyName,
|
||||
},
|
||||
Spec: policyv1alpha1.OverrideSpec{
|
||||
ResourceSelectors: rsSelectors,
|
||||
OverrideRules: overrideRules,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewFederatedResourceQuota will build a demo FederatedResourceQuota object.
|
||||
func NewFederatedResourceQuota(ns, name string) *policyv1alpha1.FederatedResourceQuota {
|
||||
return &policyv1alpha1.FederatedResourceQuota{
|
||||
|
|
|
|||
Loading…
Reference in New Issue