-
Notifications
You must be signed in to change notification settings - Fork 273
Adds basic skeleton and tests for agentmanagement #5373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package agentmanagement_test | ||
|
|
||
| import ( | ||
| . "github.com/onsi/gomega" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| rbacv1 "k8s.io/api/rbac/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| ) | ||
|
|
||
| func newNamespace(name string) *corev1.Namespace { | ||
| return &corev1.Namespace{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: name}, | ||
| } | ||
| } | ||
|
|
||
| // clusterRole returns a ClusterRole with the given name (cluster-scoped). | ||
| func clusterRole(name string) *rbacv1.ClusterRole { | ||
| return &rbacv1.ClusterRole{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: name}, | ||
| } | ||
| } | ||
|
|
||
| // objectExists returns an AsyncAssertion that succeeds when the object can be | ||
| // fetched from the API server. | ||
| func objectExists(obj client.Object) AsyncAssertion { | ||
| key := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} | ||
| return Eventually(func(g Gomega) { | ||
| g.Expect(k8sClient.Get(ctx, key, obj)).To(Succeed()) | ||
| }) | ||
| } | ||
|
|
||
| // namespaceExists returns an AsyncAssertion that succeeds when a namespace with | ||
| // the given name exists. | ||
| func namespaceExists(name string) AsyncAssertion { | ||
| return objectExists(newNamespace(name)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| package agentmanagement_test | ||
|
|
||
| // Tests for internal/cmd/controller/agentmanagement/controllers/resources. | ||
| // | ||
| // ApplyBootstrapResources is a one-shot synchronous call made during | ||
| // controllers.Register (no watch loop). The frozen contract asserts: | ||
| // - exact PolicyRule content on both ClusterRoles | ||
| // - both namespaces created | ||
| // - objects are stable (Consistently) — no lifecycle management after startup | ||
| // | ||
| // No-prune path: the object set is fixed (always the same 4 objects); | ||
| // there is no varying input that would trigger GC of an orphan. Wrangler | ||
| // apply idempotency is exercised by the suite running a second time on a | ||
| // fresh envtest (objects re-created cleanly on each run). | ||
|
|
||
| import ( | ||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
|
|
||
| "github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/resources" | ||
| fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| rbacv1 "k8s.io/api/rbac/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| ) | ||
|
|
||
| var _ = Describe("resources.ApplyBootstrapResources", func() { | ||
| // registrationNS is derived from systemNamespace per fleetns.SystemRegistrationNamespace. | ||
| const registrationNS = "cattle-fleet-clusters-system" | ||
|
|
||
| Describe("system and registration namespaces", func() { | ||
| It("creates the system namespace", func() { | ||
| namespaceExists(systemNamespace).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("creates the system registration namespace", func() { | ||
| namespaceExists(registrationNS).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("keeps both namespaces stable (no lifecycle management after startup)", func() { | ||
| Consistently(func(g Gomega) { | ||
| ns := &corev1.Namespace{} | ||
| g.Expect(k8sClient.Get(ctx, | ||
| types.NamespacedName{Name: systemNamespace}, ns)).To(Succeed()) | ||
| g.Expect(k8sClient.Get(ctx, | ||
| types.NamespacedName{Name: registrationNS}, ns)).To(Succeed()) | ||
| }).Should(Succeed()) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("fleet-bundle-deployment ClusterRole", func() { | ||
| It("exists", func() { | ||
| objectExists(clusterRole(resources.BundleDeploymentClusterRole)).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("has exactly the expected policy rules", func() { | ||
| cr := &rbacv1.ClusterRole{} | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: resources.BundleDeploymentClusterRole, | ||
| }, cr)).To(Succeed()) | ||
| }).Should(Succeed()) | ||
|
|
||
| Expect(cr.Rules).To(ConsistOf( | ||
| rbacv1.PolicyRule{ | ||
| Verbs: []string{"get", "list", "watch"}, | ||
| APIGroups: []string{fleet.SchemeGroupVersion.Group}, | ||
| Resources: []string{fleet.BundleDeploymentResourceNamePlural}, | ||
| }, | ||
| rbacv1.PolicyRule{ | ||
| Verbs: []string{"update", "patch"}, | ||
| APIGroups: []string{fleet.SchemeGroupVersion.Group}, | ||
| Resources: []string{fleet.BundleDeploymentResourceNamePlural + "/status"}, | ||
| }, | ||
| rbacv1.PolicyRule{ | ||
| Verbs: []string{"get"}, | ||
| APIGroups: []string{""}, | ||
| Resources: []string{"secrets", "configmaps"}, | ||
| }, | ||
| )) | ||
| }) | ||
|
|
||
| It("is stable (rules do not drift)", func() { | ||
| Consistently(func(g Gomega) { | ||
| cr := &rbacv1.ClusterRole{} | ||
| g.Expect(k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: resources.BundleDeploymentClusterRole, | ||
| }, cr)).To(Succeed()) | ||
| g.Expect(cr.Rules).To(HaveLen(3)) | ||
| }).Should(Succeed()) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("fleet-content ClusterRole", func() { | ||
| It("exists", func() { | ||
| objectExists(clusterRole(resources.ContentClusterRole)).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("has exactly the expected policy rule", func() { | ||
| cr := &rbacv1.ClusterRole{} | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: resources.ContentClusterRole, | ||
| }, cr)).To(Succeed()) | ||
| }).Should(Succeed()) | ||
|
|
||
| Expect(cr.Rules).To(ConsistOf( | ||
| rbacv1.PolicyRule{ | ||
| Verbs: []string{"get"}, | ||
| APIGroups: []string{fleet.SchemeGroupVersion.Group}, | ||
| Resources: []string{fleet.ContentResourceNamePlural}, | ||
| }, | ||
| )) | ||
| }) | ||
|
|
||
| It("is stable (rules do not drift)", func() { | ||
| Consistently(func(g Gomega) { | ||
| cr := &rbacv1.ClusterRole{} | ||
| g.Expect(k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: resources.ContentClusterRole, | ||
| }, cr)).To(Succeed()) | ||
| g.Expect(cr.Rules).To(HaveLen(1)) | ||
| }).Should(Succeed()) | ||
| }) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package agentmanagement_test | ||
|
|
||
| import ( | ||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
|
|
||
| "github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/resources" | ||
| "github.com/rancher/fleet/internal/config" | ||
| ) | ||
|
|
||
| var _ = Describe("AgentManagement harness smoke test", func() { | ||
| It("starts the controllers without error", func() { | ||
| // Reaching here means BeforeSuite completed — envtest started, | ||
| // Wrangler controllers registered and started. | ||
| Expect(true).To(BeTrue()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comment; I was confused here for a bit 😅 |
||
| }) | ||
|
|
||
| It("sets global config after controller startup", func() { | ||
| // config.Register (called inside controllers.Register) does an | ||
| // initial config.Lookup and calls config.SetAndTrigger so | ||
| // config.Get() must return a non-nil value here. | ||
|
Comment on lines
+19
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except |
||
| Expect(config.Get()).NotTo(BeNil()) | ||
| }) | ||
|
|
||
| It("creates the system namespace via ApplyBootstrapResources", func() { | ||
| // resources.ApplyBootstrapResources runs synchronously in Register. | ||
| namespaceExists(systemNamespace).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("creates the system registration namespace via ApplyBootstrapResources", func() { | ||
| // The registration namespace is derived from the system namespace: | ||
| // "cattle-fleet-system" → "cattle-fleet-clusters-system". | ||
| regNS := "cattle-fleet-clusters-system" | ||
| namespaceExists(regNS).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("creates the fleet-bundle-deployment ClusterRole", func() { | ||
| cr := clusterRole(resources.BundleDeploymentClusterRole) | ||
| objectExists(cr).Should(Succeed()) | ||
| }) | ||
|
|
||
| It("creates the fleet-content ClusterRole", func() { | ||
| cr := clusterRole(resources.ContentClusterRole) | ||
| objectExists(cr).Should(Succeed()) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Package agentmanagement_test contains behavioral (envtest) integration tests | ||
| // for the agentmanagement controllers. They exercise the controllers against a | ||
| // real API server and assert on the resulting cluster state. | ||
| // | ||
| // # Running | ||
| // | ||
| // KUBEBUILDER_ASSETS=$(setup-envtest use --use-env -p path 1.34) \ | ||
| // ginkgo ./integrationtests/agentmanagement/... | ||
| // | ||
| // # Coverage | ||
| // | ||
| // go test -coverprofile=cover.out \ | ||
| // -coverpkg=github.com/rancher/fleet/internal/cmd/controller/agentmanagement/... \ | ||
| // github.com/rancher/fleet/integrationtests/agentmanagement | ||
| package agentmanagement_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| "github.com/rancher/wrangler/v3/pkg/schemes" | ||
|
|
||
| "github.com/rancher/fleet/integrationtests/utils" | ||
| "github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers" | ||
| "github.com/rancher/fleet/internal/config" | ||
|
|
||
| appsv1 "k8s.io/api/apps/v1" | ||
| policyv1 "k8s.io/api/policy/v1" | ||
| schedulingv1 "k8s.io/api/scheduling/v1" | ||
| "k8s.io/client-go/rest" | ||
| "k8s.io/client-go/tools/clientcmd" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| "sigs.k8s.io/controller-runtime/pkg/envtest" | ||
| ) | ||
|
|
||
| var ( | ||
| cancel context.CancelFunc | ||
| cfg *rest.Config | ||
| ctx context.Context | ||
| k8sClient client.Client | ||
| testenv *envtest.Environment | ||
| ) | ||
|
|
||
| // systemNamespace is the Fleet controller namespace used across all specs. | ||
| const systemNamespace = "cattle-fleet-system" | ||
|
|
||
| func TestFleet(t *testing.T) { | ||
| RegisterFailHandler(Fail) | ||
| RunSpecs(t, "Fleet AgentManagement Suite") | ||
| } | ||
|
|
||
| var _ = BeforeSuite(func() { | ||
| utils.SuppressLogs() | ||
| ctx, cancel = context.WithCancel(context.TODO()) | ||
|
|
||
| // CRDs are two levels above this package: | ||
| // integrationtests/agentmanagement/ → ../.. → repo root | ||
| testenv = utils.NewEnvTest("../..") | ||
|
|
||
| var err error | ||
| cfg, err = utils.StartTestEnv(testenv) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| k8sClient, err = utils.NewClient(cfg) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| // Register additional types that manageagent apply-objects need | ||
| // (mirrors what production start.go registers via schemes.Register). | ||
| Expect(schemes.Register(appsv1.AddToScheme)).To(Succeed()) | ||
| Expect(schemes.Register(policyv1.AddToScheme)).To(Succeed()) | ||
| Expect(schemes.Register(schedulingv1.AddToScheme)).To(Succeed()) | ||
|
|
||
| // Seed global config before any controller fires so config.Get() never panics. | ||
| Expect(config.SetAndTrigger(config.DefaultConfig())).To(Succeed()) | ||
|
|
||
| // Build a clientcmd.ClientConfig wrapping the envtest *rest.Config so | ||
| // controllers.NewAppContext (which calls cfg.ClientConfig() internally) | ||
| // can use it. utils.FromEnvTestConfig serialises the cert material into | ||
| // a valid kubeconfig; no production seam is needed. | ||
| kubeconfigBytes := utils.FromEnvTestConfig(cfg) | ||
| clientCfg, err := clientcmd.NewClientConfigFromBytes(kubeconfigBytes) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| appCtx, err := controllers.NewAppContext(clientCfg) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| // Register and start the agentmanagement controllers, with bootstrap | ||
| // disabled so the suite does not require local-cluster wiring. | ||
| err = controllers.Register(ctx, appCtx, systemNamespace, | ||
| true, /* disableBootstrap */ | ||
| true /* enforceTTL */) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| }) | ||
|
|
||
| var _ = AfterSuite(func() { | ||
| cancel() | ||
| Expect(testenv.Stop()).ToNot(HaveOccurred()) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this redundant with the smoke tests?