diff --git a/manifests/klusterlet/management/klusterlet-agent-deployment.yaml b/manifests/klusterlet/management/klusterlet-agent-deployment.yaml index 3a5f305d1..b4c278f9d 100644 --- a/manifests/klusterlet/management/klusterlet-agent-deployment.yaml +++ b/manifests/klusterlet/management/klusterlet-agent-deployment.yaml @@ -107,6 +107,12 @@ spec: {{if gt .AgentKubeAPIBurst 0}} - "--kube-api-burst={{ .AgentKubeAPIBurst }}" {{end}} + {{if .MaxCustomClusterClaims}} + - "--max-custom-cluster-claims={{ .MaxCustomClusterClaims }}" + {{end}} + {{if .ReservedClusterClaimSuffixes}} + - "--reserved-cluster-claim-suffixes={{ .ReservedClusterClaimSuffixes }}" + {{end}} {{if .AppliedManifestWorkEvictionGracePeriod}} - "--appliedmanifestwork-eviction-grace-period={{ .AppliedManifestWorkEvictionGracePeriod }}" {{end}} diff --git a/manifests/klusterlet/management/klusterlet-registration-deployment.yaml b/manifests/klusterlet/management/klusterlet-registration-deployment.yaml index 69f4d713e..c3f1cb646 100644 --- a/manifests/klusterlet/management/klusterlet-registration-deployment.yaml +++ b/manifests/klusterlet/management/klusterlet-registration-deployment.yaml @@ -94,6 +94,12 @@ spec: {{if gt .RegistrationKubeAPIBurst 0}} - "--kube-api-burst={{ .RegistrationKubeAPIBurst }}" {{end}} + {{if .MaxCustomClusterClaims}} + - "--max-custom-cluster-claims={{ .MaxCustomClusterClaims }}" + {{end}} + {{if .ReservedClusterClaimSuffixes}} + - "--reserved-cluster-claim-suffixes={{ .ReservedClusterClaimSuffixes }}" + {{end}} {{if eq .RegistrationDriver.AuthType "awsirsa"}} - "--registration-auth={{ .RegistrationDriver.AuthType }}" - "--hub-cluster-arn={{ .RegistrationDriver.AwsIrsa.HubClusterArn }}" diff --git a/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go b/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go index 2d6cb80b0..102fc7f06 100644 --- a/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go +++ b/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go @@ -181,6 +181,8 @@ type klusterletConfig struct { ExternalManagedKubeConfigAgentSecret string InstallMode operatorapiv1.InstallMode + MaxCustomClusterClaims int + ReservedClusterClaimSuffixes string // PriorityClassName is the name of the PriorityClass used by the deployed agents PriorityClassName string @@ -370,6 +372,14 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto AuthType: klusterlet.Spec.RegistrationConfiguration.RegistrationDriver.AuthType, } } + + // include clusterClaimConfig info if it exists + if klusterlet.Spec.RegistrationConfiguration.ClusterClaimConfiguration != nil { + config.MaxCustomClusterClaims = int(klusterlet.Spec.RegistrationConfiguration.ClusterClaimConfiguration.MaxCustomClusterClaims) + config.ReservedClusterClaimSuffixes = strings.Join( + klusterlet.Spec.RegistrationConfiguration.ClusterClaimConfiguration.ReservedClusterClaimSuffixes, ",") + } + // construct cluster annotations string, the final format is "key1=value1,key2=value2" var annotationsArray []string for k, v := range commonhelpers.FilterClusterAnnotations(klusterlet.Spec.RegistrationConfiguration.ClusterAnnotations) { diff --git a/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go b/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go index 5e3166f0c..fed220e58 100644 --- a/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go +++ b/pkg/operator/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go @@ -377,7 +377,8 @@ func getDeployments(actions []clienttesting.Action, verb, suffix string) *appsv1 return nil } -func assertKlusterletDeployment(t *testing.T, actions []clienttesting.Action, verb, serverURL, clusterName string) { +func assertKlusterletDeployment(t *testing.T, registrationAuthType string, actions []clienttesting.Action, verb, serverURL, clusterName string, + claimConfig *operatorapiv1.ClusterClaimConfiguration) { deployment := getDeployments(actions, verb, "agent") if deployment == nil { t.Errorf("klusterlet deployment not found") @@ -399,24 +400,39 @@ func assertKlusterletDeployment(t *testing.T, actions []clienttesting.Action, ve "--bootstrap-kubeconfig=/spoke/bootstrap/kubeconfig", } + expectedArgs = append(expectedArgs, "--agent-id=", "--workload-source-driver=kube", "--workload-source-config=/spoke/hub-kubeconfig/kubeconfig", + "--status-sync-interval=60s", "--kube-api-qps=20", "--kube-api-burst=60") + if serverURL != "" { expectedArgs = append(expectedArgs, fmt.Sprintf("--spoke-external-server-urls=%s", serverURL)) } - expectedArgs = append(expectedArgs, "--agent-id=", "--workload-source-driver=kube", "--workload-source-config=/spoke/hub-kubeconfig/kubeconfig", - "--status-sync-interval=60s", "--kube-api-qps=20", "--kube-api-burst=60", - "--registration-auth=awsirsa", - "--hub-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1", - "--managed-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1", - "--managed-cluster-role-suffix=7f8141296c75f2871e3d030f85c35692") + if claimConfig != nil { + if claimConfig.MaxCustomClusterClaims > 0 { + expectedArgs = append(expectedArgs, fmt.Sprintf("--max-custom-cluster-claims=%d", claimConfig.MaxCustomClusterClaims)) + } + if len(claimConfig.ReservedClusterClaimSuffixes) > 0 { + expectedArgs = append(expectedArgs, fmt.Sprintf("--reserved-cluster-claim-suffixes=%s", + strings.Join(claimConfig.ReservedClusterClaimSuffixes, ","))) + } + } + if registrationAuthType == "awsirsa" { + expectedArgs = append(expectedArgs, + "--registration-auth=awsirsa", + "--hub-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/hub-cluster1", + "--managed-cluster-arn=arn:aws:eks:us-west-2:123456789012:cluster/managed-cluster1", + "--managed-cluster-role-suffix=7f8141296c75f2871e3d030f85c35692") + } if !equality.Semantic.DeepEqual(args, expectedArgs) { t.Errorf("Expect args %v, but got %v", expectedArgs, args) return } - assert.True(t, isDotAwsMounted(volumeMounts)) - assert.True(t, isDotAwsVolumePresent(volumes)) + if registrationAuthType == "awsirsa" { + assert.True(t, isDotAwsMounted(volumeMounts)) + assert.True(t, isDotAwsVolumePresent(volumes)) + } } @@ -1075,7 +1091,7 @@ func TestAWSIrsaAuthInSingletonMode(t *testing.T) { t.Errorf("Expected non error when sync, %v", err) } - assertKlusterletDeployment(t, controller.kubeClient.Actions(), createVerb, "", "cluster1") + assertKlusterletDeployment(t, commonhelpers.AwsIrsaAuthType, controller.kubeClient.Actions(), createVerb, "", "cluster1", nil) } func TestAWSIrsaAuthInNonSingletonMode(t *testing.T) { @@ -1413,6 +1429,40 @@ func TestRenderingResourceRequirements(t *testing.T) { } } +func TestClusterClaimConfigInSingletonMode(t *testing.T) { + klusterlet := newKlusterlet("klusterlet", "testns", "cluster1") + claimConfig := &operatorapiv1.ClusterClaimConfiguration{ + MaxCustomClusterClaims: 2, + ReservedClusterClaimSuffixes: []string{"test1.io", "test2.io"}, + } + if klusterlet.Spec.RegistrationConfiguration == nil { + klusterlet.Spec.RegistrationConfiguration = &operatorapiv1.RegistrationConfiguration{} + } + klusterlet.Spec.RegistrationConfiguration.ClusterClaimConfiguration = claimConfig + + klusterlet.Spec.DeployOption.Mode = operatorapiv1.InstallModeSingleton + hubSecret := newSecret(helpers.HubKubeConfig, "testns") + hubSecret.Data["kubeconfig"] = []byte("dummykubeconfig") + hubSecret.Data["cluster-name"] = []byte("cluster1") + objects := []runtime.Object{ + newNamespace("testns"), + newSecret(helpers.BootstrapHubKubeConfig, "testns"), + hubSecret, + } + + syncContext := testingcommon.NewFakeSyncContext(t, "klusterlet") + controller := newTestController(t, klusterlet, syncContext.Recorder(), nil, false, + objects...) + + err := controller.controller.sync(context.TODO(), syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + + assertKlusterletDeployment(t, commonhelpers.CSRAuthType, controller.kubeClient.Actions(), createVerb, + "", "cluster1", claimConfig) +} + func newKubeConfig(host string) []byte { configData, _ := runtime.Encode(clientcmdlatest.Codec, &clientcmdapi.Config{ Clusters: map[string]*clientcmdapi.Cluster{"test-cluster": { diff --git a/pkg/registration/spoke/managedcluster/claim_reconcile.go b/pkg/registration/spoke/managedcluster/claim_reconcile.go index 78711d737..64494bbf8 100644 --- a/pkg/registration/spoke/managedcluster/claim_reconcile.go +++ b/pkg/registration/spoke/managedcluster/claim_reconcile.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "strings" "github.com/openshift/library-go/pkg/operator/events" "k8s.io/apimachinery/pkg/api/meta" @@ -22,9 +23,10 @@ import ( const labelCustomizedOnly = "open-cluster-management.io/spoke-only" type claimReconcile struct { - recorder events.Recorder - claimLister clusterv1alpha1listers.ClusterClaimLister - maxCustomClusterClaims int + recorder events.Recorder + claimLister clusterv1alpha1listers.ClusterClaimLister + maxCustomClusterClaims int + reservedClusterClaimSuffixes []string } func (r *claimReconcile) reconcile(ctx context.Context, cluster *clusterv1.ManagedCluster) (*clusterv1.ManagedCluster, reconcileState, error) { @@ -55,13 +57,17 @@ func (r *claimReconcile) exposeClaims(ctx context.Context, cluster *clusterv1.Ma return fmt.Errorf("unable to list cluster claims: %w", err) } - reservedClaimNames := sets.NewString(clusterv1alpha1.ReservedClusterClaimNames[:]...) + // check if the cluster claim is one of the reserved claims or has a reserved suffix. + // if so, it will be treated as a reserved claim and will always be exposed. + reservedClaimNames := sets.New(clusterv1alpha1.ReservedClusterClaimNames[:]...) + reservedClaimSuffixes := sets.New(r.reservedClusterClaimSuffixes...) for _, clusterClaim := range clusterClaims { managedClusterClaim := clusterv1.ManagedClusterClaim{ Name: clusterClaim.Name, Value: clusterClaim.Spec.Value, } - if reservedClaimNames.Has(clusterClaim.Name) { + + if matchReservedClaims(reservedClaimNames, reservedClaimSuffixes, managedClusterClaim) { reservedClaims = append(reservedClaims, managedClusterClaim) continue } @@ -90,3 +96,16 @@ func (r *claimReconcile) exposeClaims(ctx context.Context, cluster *clusterv1.Ma cluster.Status.ClusterClaims = claims return nil } + +func matchReservedClaims(reservedClaims, reservedSuffixes sets.Set[string], claim clusterv1.ManagedClusterClaim) bool { + if reservedClaims.Has(claim.Name) { + return true + } + + for suffix := range reservedSuffixes { + if strings.HasSuffix(claim.Name, suffix) { + return true + } + } + return false +} diff --git a/pkg/registration/spoke/managedcluster/claim_reconcile_test.go b/pkg/registration/spoke/managedcluster/claim_reconcile_test.go index 07da61c53..2ca86070c 100644 --- a/pkg/registration/spoke/managedcluster/claim_reconcile_test.go +++ b/pkg/registration/spoke/managedcluster/claim_reconcile_test.go @@ -29,8 +29,11 @@ import ( testinghelpers "open-cluster-management.io/ocm/pkg/registration/helpers/testing" ) -func TestSync(t *testing.T) { +func init() { utilruntime.Must(features.SpokeMutableFeatureGate.Add(ocmfeature.DefaultSpokeRegistrationFeatureGates)) +} + +func TestSync(t *testing.T) { cases := []struct { name string cluster runtime.Object @@ -125,6 +128,7 @@ func TestSync(t *testing.T) { clusterInformerFactory.Cluster().V1alpha1().ClusterClaims(), kubeInformerFactory.Core().V1().Nodes(), 20, + []string{}, eventstesting.NewTestingEventRecorder(t), hubEventRecorder, ) @@ -139,12 +143,13 @@ func TestSync(t *testing.T) { func TestExposeClaims(t *testing.T) { cases := []struct { - name string - cluster *clusterv1.ManagedCluster - claims []*clusterv1alpha1.ClusterClaim - maxCustomClusterClaims int - validateActions func(t *testing.T, actions []clienttesting.Action) - expectedErr string + name string + cluster *clusterv1.ManagedCluster + claims []*clusterv1alpha1.ClusterClaim + maxCustomClusterClaims int + reservedClusterClaimSuffixes []string + validateActions func(t *testing.T, actions []clienttesting.Action) + expectedErr string }{ { name: "sync claims into status of the managed cluster", @@ -207,6 +212,14 @@ func TestExposeClaims(t *testing.T) { Value: "cluster1", }, }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "id-test.k8s.io", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "cluster1", + }, + }, { ObjectMeta: metav1.ObjectMeta{ Name: "c", @@ -245,6 +258,85 @@ func TestExposeClaims(t *testing.T) { } }, }, + { + name: "keep custom reserved cluster claims", + cluster: testinghelpers.NewJoinedManagedCluster(), + claims: []*clusterv1alpha1.ClusterClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "a", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "b", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "e", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "f", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "id.k8s.io", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "cluster1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "c", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "d", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test.reserved.io", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "test", + }, + }, + }, + maxCustomClusterClaims: 2, + reservedClusterClaimSuffixes: []string{"reserved.io"}, + validateActions: func(t *testing.T, actions []clienttesting.Action) { + testingcommon.AssertActions(t, actions, "patch") + patch := actions[0].(clienttesting.PatchAction).GetPatch() + cluster := &clusterv1.ManagedCluster{} + err := json.Unmarshal(patch, cluster) + if err != nil { + t.Fatal(err) + } + expected := []clusterv1.ManagedClusterClaim{ + { + Name: "id.k8s.io", + Value: "cluster1", + }, + { + Name: "test.reserved.io", + Value: "test", + }, + { + Name: "a", + Value: "b", + }, + { + Name: "c", + Value: "d", + }, + } + actual := cluster.Status.ClusterClaims + if !reflect.DeepEqual(actual, expected) { + t.Errorf("expected cluster claim %v but got: %v", expected, actual) + } + }, + }, { name: "remove claims from managed cluster", cluster: newManagedCluster([]clusterv1.ManagedClusterClaim{ @@ -356,6 +448,7 @@ func TestExposeClaims(t *testing.T) { clusterInformerFactory.Cluster().V1alpha1().ClusterClaims(), kubeInformerFactory.Core().V1().Nodes(), c.maxCustomClusterClaims, + c.reservedClusterClaimSuffixes, eventstesting.NewTestingEventRecorder(t), hubEventRecorder, ) diff --git a/pkg/registration/spoke/managedcluster/joining_controller_test.go b/pkg/registration/spoke/managedcluster/joining_controller_test.go index bfe25880d..e3237e50d 100644 --- a/pkg/registration/spoke/managedcluster/joining_controller_test.go +++ b/pkg/registration/spoke/managedcluster/joining_controller_test.go @@ -96,6 +96,7 @@ func TestSyncManagedCluster(t *testing.T) { clusterInformerFactory.Cluster().V1alpha1().ClusterClaims(), kubeInformerFactory.Core().V1().Nodes(), 20, + []string{}, eventstesting.NewTestingEventRecorder(t), hubEventRecorder, ) diff --git a/pkg/registration/spoke/managedcluster/resource_reconcile_test.go b/pkg/registration/spoke/managedcluster/resource_reconcile_test.go index 1bbb8c15f..abc4a626c 100644 --- a/pkg/registration/spoke/managedcluster/resource_reconcile_test.go +++ b/pkg/registration/spoke/managedcluster/resource_reconcile_test.go @@ -330,6 +330,7 @@ func TestHealthCheck(t *testing.T) { clusterInformerFactory.Cluster().V1alpha1().ClusterClaims(), kubeInformerFactory.Core().V1().Nodes(), 20, + []string{}, eventstesting.NewTestingEventRecorder(t), hubEventRecorder, ) diff --git a/pkg/registration/spoke/managedcluster/status_controller.go b/pkg/registration/spoke/managedcluster/status_controller.go index 352aadb79..6a037eceb 100644 --- a/pkg/registration/spoke/managedcluster/status_controller.go +++ b/pkg/registration/spoke/managedcluster/status_controller.go @@ -54,6 +54,7 @@ func NewManagedClusterStatusController( claimInformer clusterv1alpha1informer.ClusterClaimInformer, nodeInformer corev1informers.NodeInformer, maxCustomClusterClaims int, + reservedClusterClaimSuffixes []string, resyncInterval time.Duration, recorder events.Recorder, hubEventRecorder kevents.EventRecorder) factory.Controller { @@ -65,6 +66,7 @@ func NewManagedClusterStatusController( claimInformer, nodeInformer, maxCustomClusterClaims, + reservedClusterClaimSuffixes, recorder, hubEventRecorder, ) @@ -84,6 +86,7 @@ func newManagedClusterStatusController( claimInformer clusterv1alpha1informer.ClusterClaimInformer, nodeInformer corev1informers.NodeInformer, maxCustomClusterClaims int, + reservedClusterClaimSuffixes []string, recorder events.Recorder, hubEventRecorder kevents.EventRecorder) *managedClusterStatusController { return &managedClusterStatusController{ @@ -94,7 +97,8 @@ func newManagedClusterStatusController( reconcilers: []statusReconcile{ &joiningReconcile{recorder: recorder}, &resoureReconcile{managedClusterDiscoveryClient: managedClusterDiscoveryClient, nodeLister: nodeInformer.Lister()}, - &claimReconcile{claimLister: claimInformer.Lister(), recorder: recorder, maxCustomClusterClaims: maxCustomClusterClaims}, + &claimReconcile{claimLister: claimInformer.Lister(), recorder: recorder, + maxCustomClusterClaims: maxCustomClusterClaims, reservedClusterClaimSuffixes: reservedClusterClaimSuffixes}, }, hubClusterLister: hubClusterInformer.Lister(), hubEventRecorder: hubEventRecorder, diff --git a/pkg/registration/spoke/options.go b/pkg/registration/spoke/options.go index db2f14a9e..c3566abc4 100644 --- a/pkg/registration/spoke/options.go +++ b/pkg/registration/spoke/options.go @@ -30,11 +30,12 @@ type SpokeAgentOptions struct { // See more details in: https://github.com/open-cluster-management-io/ocm/pull/443#discussion_r1610868646 HubConnectionTimeoutSeconds int32 - HubKubeconfigSecret string - SpokeExternalServerURLs []string - ClusterHealthCheckPeriod time.Duration - MaxCustomClusterClaims int - ClusterAnnotations map[string]string + HubKubeconfigSecret string + SpokeExternalServerURLs []string + ClusterHealthCheckPeriod time.Duration + MaxCustomClusterClaims int + ReservedClusterClaimSuffixes []string + ClusterAnnotations map[string]string RegisterDriverOption *registerfactory.Options } @@ -71,6 +72,8 @@ func (o *SpokeAgentOptions) AddFlags(fs *pflag.FlagSet) { "The period to check managed cluster kube-apiserver health") fs.IntVar(&o.MaxCustomClusterClaims, "max-custom-cluster-claims", o.MaxCustomClusterClaims, "The max number of custom cluster claims to expose.") + fs.StringSliceVar(&o.ReservedClusterClaimSuffixes, "reserved-cluster-claim-suffixes", o.ReservedClusterClaimSuffixes, + "A list of suffixes for reserved cluster claims.") fs.StringToStringVar(&o.ClusterAnnotations, "cluster-annotations", o.ClusterAnnotations, `the annotations with the reserve prefix "agent.open-cluster-management.io" set on ManagedCluster when creating only, other actors can update it afterwards.`) diff --git a/pkg/registration/spoke/spokeagent.go b/pkg/registration/spoke/spokeagent.go index fcdd226e8..436f341ff 100644 --- a/pkg/registration/spoke/spokeagent.go +++ b/pkg/registration/spoke/spokeagent.go @@ -358,6 +358,7 @@ func (o *SpokeAgentConfig) RunSpokeAgentWithSpokeInformers(ctx context.Context, spokeClusterInformerFactory.Cluster().V1alpha1().ClusterClaims(), spokeKubeInformerFactory.Core().V1().Nodes(), o.registrationOption.MaxCustomClusterClaims, + o.registrationOption.ReservedClusterClaimSuffixes, o.registrationOption.ClusterHealthCheckPeriod, recorder, hubEventRecorder, diff --git a/test/integration/operator/klusterlet_test.go b/test/integration/operator/klusterlet_test.go index 7ad3265ec..5e217f883 100644 --- a/test/integration/operator/klusterlet_test.go +++ b/test/integration/operator/klusterlet_test.go @@ -789,6 +789,74 @@ var _ = ginkgo.Describe("Klusterlet", func() { return true }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) }) + + ginkgo.It("Deployment should be updated when klusterlet claim configure is changed", func() { + _, err := operatorClient.OperatorV1().Klusterlets().Create(context.Background(), klusterlet, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), workDeploymentName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check if generations are correct + gomega.Eventually(func() bool { + actual, err := operatorClient.OperatorV1().Klusterlets().Get(context.Background(), klusterlet.Name, metav1.GetOptions{}) + if err != nil { + return false + } + + if actual.Generation != actual.Status.ObservedGeneration { + return false + } + + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + gomega.Eventually(func() error { + klusterlet, err = operatorClient.OperatorV1().Klusterlets().Get(context.Background(), klusterlet.Name, metav1.GetOptions{}) + if err != nil { + return err + } + klusterlet.Spec.RegistrationConfiguration = &operatorapiv1.RegistrationConfiguration{ + ClusterClaimConfiguration: &operatorapiv1.ClusterClaimConfiguration{ + MaxCustomClusterClaims: 2, + ReservedClusterClaimSuffixes: []string{"reserved1.io", "reserved2.io"}, + }, + } + _, err = operatorClient.OperatorV1().Klusterlets().Update(context.Background(), klusterlet, metav1.UpdateOptions{}) + return err + }, eventuallyTimeout, eventuallyInterval).Should(gomega.Succeed()) + + gomega.Eventually(func() bool { + actual, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), registrationDeploymentName, metav1.GetOptions{}) + if err != nil { + return false + } + gomega.Expect(len(actual.Spec.Template.Spec.Containers)).Should(gomega.Equal(1)) + if len(actual.Spec.Template.Spec.Containers[0].Args) != 9 { + return false + } + return actual.Spec.Template.Spec.Containers[0].Args[7] == "--max-custom-cluster-claims=2" && + actual.Spec.Template.Spec.Containers[0].Args[8] == "--reserved-cluster-claim-suffixes=reserved1.io,reserved2.io" + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check if generations are correct + gomega.Eventually(func() bool { + actual, err := operatorClient.OperatorV1().Klusterlets().Get(context.Background(), klusterlet.Name, metav1.GetOptions{}) + if err != nil { + return false + } + + if actual.Generation != actual.Status.ObservedGeneration { + return false + } + + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + }) }) ginkgo.Context("klusterlet statuses", func() { diff --git a/test/integration/registration/spokecluster_claim_test.go b/test/integration/registration/spokecluster_claim_test.go index e8e2081ba..9cf18a03a 100644 --- a/test/integration/registration/spokecluster_claim_test.go +++ b/test/integration/registration/spokecluster_claim_test.go @@ -27,6 +27,7 @@ var _ = ginkgo.Describe("Cluster Claim", func() { var managedClusterName, hubKubeconfigSecret, hubKubeconfigDir string var claims []*clusterv1alpha1.ClusterClaim var maxCustomClusterClaims int + var reservedClusterClaimSuffixes []string var err error var cancel context.CancelFunc @@ -52,11 +53,12 @@ var _ = ginkgo.Describe("Cluster Claim", func() { // run registration agent agentOptions := &spoke.SpokeAgentOptions{ - BootstrapKubeconfig: bootstrapKubeConfigFile, - HubKubeconfigSecret: hubKubeconfigSecret, - ClusterHealthCheckPeriod: 1 * time.Minute, - MaxCustomClusterClaims: maxCustomClusterClaims, - RegisterDriverOption: registerfactory.NewOptions(), + BootstrapKubeconfig: bootstrapKubeConfigFile, + HubKubeconfigSecret: hubKubeconfigSecret, + ClusterHealthCheckPeriod: 1 * time.Minute, + MaxCustomClusterClaims: maxCustomClusterClaims, + ReservedClusterClaimSuffixes: reservedClusterClaimSuffixes, + RegisterDriverOption: registerfactory.NewOptions(), } commOptions := commonoptions.NewAgentOptions() commOptions.HubKubeconfigDir = hubKubeconfigDir @@ -271,4 +273,45 @@ var _ = ginkgo.Describe("Cluster Claim", func() { }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) }) }) + + ginkgo.Context("Keep custom reserved claims", func() { + ginkgo.BeforeEach(func() { + maxCustomClusterClaims = 5 + reservedClusterClaimSuffixes = []string{"reserved.io"} + claims = []*clusterv1alpha1.ClusterClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "claim.reserved.io", + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: "value-test", + }, + }, + } + for i := 0; i < 10; i++ { + claims = append(claims, &clusterv1alpha1.ClusterClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("claim-%d", i), + }, + Spec: clusterv1alpha1.ClusterClaimSpec{ + Value: fmt.Sprintf("value-%d", i), + }, + }) + } + }) + + ginkgo.It("should sync custom suffix claims to status of ManagedCluster", func() { + assertSuccessBootstrap() + + ginkgo.By("Sync claims") + gomega.Eventually(func() bool { + spokeCluster, err := util.GetManagedCluster(clusterClient, managedClusterName) + if err != nil { + return false + } + + return len(spokeCluster.Status.ClusterClaims) == maxCustomClusterClaims+1 + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + }) + }) })