Skip to content

Commit

Permalink
feat: add clusters-to-adopt annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlihanbo committed Nov 7, 2023
1 parent 2c3b898 commit b4cd16a
Show file tree
Hide file tree
Showing 6 changed files with 205 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

Check warning on line 298 in pkg/controllers/nsautoprop/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/nsautoprop/controller.go#L292-L298

Added lines #L292 - L298 were not covered by tests
// 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)
}

Check warning on line 252 in pkg/controllers/sync/dispatch/managed.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/sync/dispatch/managed.go#L249-L252

Added lines #L249 - L252 were not covered by tests

if d.skipAdoptingResources || !canAdopt {

Check warning on line 254 in pkg/controllers/sync/dispatch/managed.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/sync/dispatch/managed.go#L254

Added line #L254 was not covered by tests
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
}
146 changes: 146 additions & 0 deletions pkg/util/adoption/conflictresolution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
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*"}`}),

Check failure on line 117 in pkg/util/adoption/conflictresolution_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
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*"}`}),

Check failure on line 127 in pkg/util/adoption/conflictresolution_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
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 b4cd16a

Please sign in to comment.