Skip to content

Commit

Permalink
Merge pull request #259 from mrlihanbo/feat/clusters-to-adopt
Browse files Browse the repository at this point in the history
feat: add clusters-to-adopt annotation
  • Loading branch information
mrlihanbo authored Nov 7, 2023
2 parents 2c3b898 + d837f7e commit 2f4b1f4
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 2 deletions.
1 change: 1 addition & 0 deletions pkg/controllers/federate/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ var (
nsautoprop.NoAutoPropagationAnnotation,
orphaning.OrphanManagedResourcesAnnotation,
adoption.ConflictResolutionAnnotation,
adoption.ClustersToAdoptAnnotation,
scheduler.TolerationsAnnotations,
scheduler.PlacementsAnnotations,
scheduler.ClusterSelectorAnnotations,
Expand Down
6 changes: 5 additions & 1 deletion pkg/controllers/nsautoprop/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ limitations under the License.

package nsautoprop

import "github.com/kubewharf/kubeadmiral/pkg/controllers/common"
import (
"github.com/kubewharf/kubeadmiral/pkg/controllers/common"
)

var NoAutoPropagationAnnotation = common.DefaultPrefix + "no-auto-propagation"

const AllClustersToAdoptRegexp = `{"regexp":".*"}`
7 changes: 7 additions & 0 deletions pkg/controllers/nsautoprop/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ func (c *Controller) reconcile(ctx context.Context, qualifiedName common.Qualifi
}
needsUpdate = needsUpdate || isDirty

isDirty, err = c.ensureAnnotation(fedNamespace, adoption.ClustersToAdoptAnnotation, AllClustersToAdoptRegexp)
if err != nil {
keyedLogger.Error(err, "Failed to ensure annotation")
return worker.StatusError
}
needsUpdate = needsUpdate || isDirty

// Ensure we don't delete adopted member namespaces when the federated namespace is deleted
isDirty, err = c.ensureAnnotation(
fedNamespace,
Expand Down
7 changes: 6 additions & 1 deletion pkg/controllers/sync/dispatch/managed.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,12 @@ func (d *managedDispatcherImpl) Create(ctx context.Context, clusterName string)
return d.recordOperationError(ctxWithTimeout, fedcorev1a1.RetrievalFailed, clusterName, op, wrappedErr)
}

if d.skipAdoptingResources {
canAdopt, err := adoption.FilterToAdoptCluster(d.fedResource.Object(), clusterName)
if err != nil {
return d.recordOperationError(ctxWithTimeout, fedcorev1a1.AlreadyExists, clusterName, op, err)
}

if d.skipAdoptingResources || !canAdopt {
result = d.recordOperationError(
ctxWithTimeout,
fedcorev1a1.AlreadyExists,
Expand Down
40 changes: 40 additions & 0 deletions pkg/util/adoption/conflictresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ limitations under the License.
package adoption

import (
"encoding/json"
"fmt"
"regexp"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kubewharf/kubeadmiral/pkg/controllers/common"
Expand All @@ -25,6 +29,9 @@ import (
const (
ConflictResolutionAnnotation = common.DefaultPrefix + "conflict-resolution"
ConflictResolutionInternalAnnotation = common.InternalPrefix + "conflict-resolution"
// ClustersToAdoptAnnotation specifies the set of clusters where preexisting resources are allowed to be adopted. Defaults to no clusters.
// It will only take effect if adoption is enabled by the conflict resolution annotation.
ClustersToAdoptAnnotation = common.DefaultPrefix + "clusters-to-adopt"
)

type ConflictResolution string
Expand All @@ -44,3 +51,36 @@ func ShouldAdoptPreexistingResources(obj metav1.Object) bool {

return value == string(ConflictResolutionAdopt)
}

type clustersToAdoptAnnotationElement struct {
Clusters []string `json:"clusters,omitempty"`
Regexp string `json:"regexp,omitempty"`
}

func FilterToAdoptCluster(obj metav1.Object, clusterName string) (bool, error) {
annotation := obj.GetAnnotations()[ClustersToAdoptAnnotation]
if len(annotation) == 0 {
return false, nil
}

var clustersToAdopt clustersToAdoptAnnotationElement
if err := json.Unmarshal([]byte(annotation), &clustersToAdopt); err != nil {
return false, fmt.Errorf("failed to unmarshal %s annotation %w", ClustersToAdoptAnnotation, err)
}

for _, name := range clustersToAdopt.Clusters {
if name == clusterName {
return true, nil
}
}

if len(clustersToAdopt.Regexp) != 0 {
clustersToAdoptRegexp, err := regexp.Compile(clustersToAdopt.Regexp)
if err != nil {
return false, fmt.Errorf("failed to compile regexp for to adopt clusters: %w", err)
}
return clustersToAdoptRegexp.MatchString(clusterName), nil
}

return false, nil
}
148 changes: 148 additions & 0 deletions pkg/util/adoption/conflictresolution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
Copyright 2023 The KubeAdmiral Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package adoption

import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1"
)

func newFederatedObject(annotations map[string]string) *fedcorev1a1.FederatedObject {
return &fedcorev1a1.FederatedObject{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Annotations: annotations,
},
Spec: fedcorev1a1.GenericFederatedObjectSpec{
Placements: []fedcorev1a1.PlacementWithController{
{
Controller: "test-controller",
Placement: []fedcorev1a1.ClusterReference{
{Cluster: "kubeadmiral-member-1"},
{Cluster: "kubeadmiral-member-2"},
{Cluster: "kubeadmiral-member-3"},
},
},
},
},
}
}

func TestFilterToAdoptCluster(t *testing.T) {
type args struct {
obj metav1.Object
clusterName string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "propagate resource without adopting",
args: args{
obj: newFederatedObject(nil),
clusterName: "kubeadmiral-member-1",
},
want: false,
wantErr: false,
},
{
name: "invalid annotation for clusters to adopt",
args: args{
obj: newFederatedObject(map[string]string{ClustersToAdoptAnnotation: "annotation"}),
clusterName: "kubeadmiral-member-1",
},
want: false,
wantErr: true,
},
{
name: "cluster not matched",
args: args{
obj: newFederatedObject(map[string]string{ClustersToAdoptAnnotation: `{"clusters": ["kubeadmiral-member-1"]}`}),
clusterName: "kubeadmiral-member-2",
},
want: false,
wantErr: false,
},
{
name: "cluster name matched",
args: args{
obj: newFederatedObject(map[string]string{ClustersToAdoptAnnotation: `{"clusters": ["kubeadmiral-member-1"]}`}),
clusterName: "kubeadmiral-member-1",
},
want: true,
wantErr: false,
},
{
name: "invalid regexp",
args: args{
obj: newFederatedObject(map[string]string{ClustersToAdoptAnnotation: `{"regexp": "*"}`}),
clusterName: "kubeadmiral-member-1",
},
want: false,
wantErr: true,
},
{
name: "regexp matched",
args: args{
obj: newFederatedObject(map[string]string{ClustersToAdoptAnnotation: `{"regexp": ".*"}`}),
clusterName: "kubeadmiral-member-1",
},
want: true,
wantErr: false,
},
{
name: "name matched and regexp doesn't match",
args: args{
obj: newFederatedObject(map[string]string{
ClustersToAdoptAnnotation: `{"clusters": ["kubeadmiral-member-1"], "regexp": "test*"}`,
}),
clusterName: "kubeadmiral-member-1",
},
want: true,
wantErr: false,
},
{
name: "regexp matched and name doesn't match",
args: args{
obj: newFederatedObject(map[string]string{
ClustersToAdoptAnnotation: `{"clusters": ["kubeadmiral-member-2"], "regexp": "kubeadmiral*"}`,
}),
clusterName: "kubeadmiral-member-1",
},
want: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FilterToAdoptCluster(tt.args.obj, tt.args.clusterName)
if (err != nil) != tt.wantErr {
t.Errorf("FilterToAdoptCluster() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("FilterToAdoptCluster() got = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 2f4b1f4

Please sign in to comment.