Skip to content

Commit 97f87e1

Browse files
committed
agentmanagement. Ports config controler to controller-runtime
This PR is the first of a series to port the `agentmanagement` component to use controller-runtime. It creates the foundation to the rest of changes and marks the way iterations will be executed. The first step is to add testing coverage to the component to be ported, still running with `wrangler` then it ports the component and, without changing the tests, it verifies that nothing was broken. Signed-off-by: Xavi Garcia <xavi.garcia@suse.com>
1 parent 6bbc1ff commit 97f87e1

5 files changed

Lines changed: 265 additions & 9 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package config_test
2+
3+
import (
4+
"encoding/json"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
9+
"github.com/rancher/fleet/internal/config"
10+
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/types"
14+
)
15+
16+
var _ = Describe("ConfigReconciler", func() {
17+
var cm *corev1.ConfigMap
18+
19+
BeforeEach(func() {
20+
cm = &corev1.ConfigMap{
21+
ObjectMeta: metav1.ObjectMeta{
22+
Name: config.ManagerConfigName,
23+
Namespace: systemNamespace,
24+
},
25+
}
26+
})
27+
28+
AfterEach(func() {
29+
_ = k8sClient.Delete(ctx, cm)
30+
})
31+
32+
It("loads config when ConfigMap is created", func() {
33+
data, err := json.Marshal(config.Config{
34+
AgentImage: "rancher/fleet-agent:test",
35+
})
36+
Expect(err).NotTo(HaveOccurred())
37+
38+
cm.Data = map[string]string{config.Key: string(data)}
39+
Expect(k8sClient.Create(ctx, cm)).To(Succeed())
40+
41+
Eventually(func(g Gomega) {
42+
g.Expect(config.Get().AgentImage).To(Equal("rancher/fleet-agent:test"))
43+
}).Should(Succeed())
44+
})
45+
46+
It("reloads config when ConfigMap is updated", func() {
47+
data, err := json.Marshal(config.Config{
48+
AgentImage: "rancher/fleet-agent:v1",
49+
})
50+
Expect(err).NotTo(HaveOccurred())
51+
52+
cm.Data = map[string]string{config.Key: string(data)}
53+
Expect(k8sClient.Create(ctx, cm)).To(Succeed())
54+
55+
Eventually(func(g Gomega) {
56+
g.Expect(config.Get().AgentImage).To(Equal("rancher/fleet-agent:v1"))
57+
}).Should(Succeed())
58+
59+
// Update the ConfigMap to a new value
60+
data, err = json.Marshal(config.Config{
61+
AgentImage: "rancher/fleet-agent:v2",
62+
})
63+
Expect(err).NotTo(HaveOccurred())
64+
65+
Expect(k8sClient.Get(ctx, types.NamespacedName{
66+
Namespace: systemNamespace,
67+
Name: config.ManagerConfigName,
68+
}, cm)).To(Succeed())
69+
70+
cm.Data = map[string]string{config.Key: string(data)}
71+
Expect(k8sClient.Update(ctx, cm)).To(Succeed())
72+
73+
Eventually(func(g Gomega) {
74+
g.Expect(config.Get().AgentImage).To(Equal("rancher/fleet-agent:v2"))
75+
}).Should(Succeed())
76+
})
77+
})
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package config_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
10+
"github.com/rancher/fleet/integrationtests/utils"
11+
agentconfig "github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/config"
12+
"github.com/rancher/fleet/internal/config"
13+
14+
corev1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/client-go/rest"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/envtest"
19+
)
20+
21+
const systemNamespace = "cattle-fleet-system"
22+
23+
var (
24+
cfg *rest.Config
25+
testEnv *envtest.Environment
26+
ctx context.Context
27+
cancel context.CancelFunc
28+
k8sClient client.Client
29+
)
30+
31+
func TestController(t *testing.T) {
32+
RegisterFailHandler(Fail)
33+
RunSpecs(t, "AgentManagement Config Suite")
34+
}
35+
36+
var _ = BeforeSuite(func() {
37+
ctx, cancel = context.WithCancel(context.Background())
38+
testEnv = utils.NewEnvTest("../../..")
39+
40+
var err error
41+
cfg, err = utils.StartTestEnv(testEnv)
42+
Expect(err).NotTo(HaveOccurred())
43+
44+
k8sClient, err = utils.NewClient(cfg)
45+
Expect(err).NotTo(HaveOccurred())
46+
47+
// Initialize global config to prevent config.Get() panics during test setup.
48+
config.Set(config.DefaultConfig())
49+
50+
// Create system namespace before starting the manager
51+
ns := &corev1.Namespace{
52+
ObjectMeta: metav1.ObjectMeta{Name: systemNamespace},
53+
}
54+
Expect(k8sClient.Create(ctx, ns)).To(Succeed())
55+
56+
mgr, err := utils.NewManager(cfg)
57+
Expect(err).NotTo(HaveOccurred())
58+
59+
err = (&agentconfig.ConfigReconciler{
60+
Client: mgr.GetClient(),
61+
Scheme: mgr.GetScheme(),
62+
SystemNamespace: systemNamespace,
63+
}).SetupWithManager(mgr)
64+
Expect(err).NotTo(HaveOccurred())
65+
66+
go func() {
67+
defer GinkgoRecover()
68+
err = mgr.Start(ctx)
69+
Expect(err).NotTo(HaveOccurred())
70+
}()
71+
})
72+
73+
var _ = AfterSuite(func() {
74+
cancel()
75+
Expect(testEnv.Stop()).ToNot(HaveOccurred())
76+
})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Package config reads the initial global configuration.
2+
package config
3+
4+
import (
5+
"context"
6+
7+
"github.com/rancher/fleet/internal/config"
8+
9+
corev1 "k8s.io/api/core/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/types"
12+
ctrl "sigs.k8s.io/controller-runtime"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
"sigs.k8s.io/controller-runtime/pkg/log"
15+
"sigs.k8s.io/controller-runtime/pkg/predicate"
16+
)
17+
18+
// ConfigReconciler reconciles the Fleet config object for agentmanagement,
19+
// by reloading the config on change.
20+
type ConfigReconciler struct {
21+
client.Client
22+
Scheme *runtime.Scheme
23+
24+
SystemNamespace string
25+
}
26+
27+
// SetupWithManager sets up the controller with the Manager.
28+
func (r *ConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
29+
return ctrl.NewControllerManagedBy(mgr).
30+
For(&corev1.ConfigMap{}).
31+
WithEventFilter(
32+
predicate.And(
33+
predicate.NewPredicateFuncs(func(object client.Object) bool {
34+
return object.GetNamespace() == r.SystemNamespace &&
35+
object.GetName() == config.ManagerConfigName
36+
}),
37+
predicate.Or(
38+
predicate.ResourceVersionChangedPredicate{},
39+
predicate.GenerationChangedPredicate{},
40+
predicate.AnnotationChangedPredicate{},
41+
predicate.LabelChangedPredicate{},
42+
),
43+
),
44+
).
45+
Complete(r)
46+
}
47+
48+
// Reconcile reloads the Fleet config from the ConfigMap when it changes.
49+
func (r *ConfigReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) {
50+
logger := log.FromContext(ctx).WithName("agentmanagement-config")
51+
ctx = log.IntoContext(ctx, logger)
52+
53+
cm := &corev1.ConfigMap{}
54+
err := r.Get(ctx, types.NamespacedName{Namespace: r.SystemNamespace, Name: config.ManagerConfigName}, cm)
55+
if client.IgnoreNotFound(err) != nil {
56+
return ctrl.Result{}, err
57+
}
58+
59+
logger.V(1).Info("Reconciling config configmap, loading config")
60+
61+
cfg, err := config.ReadConfig(cm)
62+
if err != nil {
63+
return ctrl.Result{}, err
64+
}
65+
66+
// SetAndTrigger is used during the wrangler-to-CR migration to ensure
67+
// wrangler components (bootstrap, cluster/import) that register config.OnChange
68+
// callbacks still receive config change notifications.
69+
// TODO: Switch to config.Set() once those wrangler components are ported (Phases 3, 8).
70+
return ctrl.Result{}, config.SetAndTrigger(cfg)
71+
}

internal/cmd/controller/agentmanagement/controllers/controllers.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/cluster"
88
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/clusterregistration"
99
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/clusterregistrationtoken"
10-
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/config"
1110
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/manageagent"
1211
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/resources"
1312
fleetns "github.com/rancher/fleet/internal/cmd/controller/namespace"
@@ -58,14 +57,6 @@ func (a *AppContext) Start(ctx context.Context) error {
5857
func Register(ctx context.Context, appCtx *AppContext, systemNamespace string, disableBootstrap bool) error {
5958
systemRegistrationNamespace := fleetns.SystemRegistrationNamespace(systemNamespace)
6059

61-
// config should be registered first to ensure the global
62-
// config is available to all components
63-
if err := config.Register(ctx,
64-
systemNamespace,
65-
appCtx.Core.ConfigMap()); err != nil {
66-
return err
67-
}
68-
6960
if err := resources.ApplyBootstrapResources(
7061
systemNamespace,
7162
systemRegistrationNamespace,

internal/cmd/controller/agentmanagement/start.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55

66
"github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers"
7+
agentconfig "github.com/rancher/fleet/internal/cmd/controller/agentmanagement/controllers/config"
8+
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
79

810
"github.com/rancher/wrangler/v3/pkg/kubeconfig"
911
"github.com/rancher/wrangler/v3/pkg/leader"
@@ -14,10 +16,22 @@ import (
1416
v1 "k8s.io/api/apps/v1"
1517
policyv1 "k8s.io/api/policy/v1"
1618
schedulingv1 "k8s.io/api/scheduling/v1"
19+
"k8s.io/apimachinery/pkg/runtime"
20+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1721
"k8s.io/client-go/kubernetes"
22+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
1823
"k8s.io/client-go/rest"
24+
ctrl "sigs.k8s.io/controller-runtime"
25+
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
1926
)
2027

28+
var agentScheme = runtime.NewScheme()
29+
30+
func init() {
31+
utilruntime.Must(clientgoscheme.AddToScheme(agentScheme))
32+
utilruntime.Must(fleet.AddToScheme(agentScheme))
33+
}
34+
2135
func start(ctx context.Context, kubeConfig, namespace string, disableBootstrap bool) error {
2236
clientConfig := kubeconfig.GetNonInteractiveClientConfig(kubeConfig)
2337
kc, err := clientConfig.ClientConfig()
@@ -47,6 +61,33 @@ func start(ctx context.Context, kubeConfig, namespace string, disableBootstrap b
4761
}
4862

4963
leader.RunOrDie(ctx, namespace, "fleet-agentmanagement-lock", k8s, func(ctx context.Context) {
64+
// Create controller-runtime manager. Leader election is disabled because
65+
// wrangler's leader.RunOrDie already holds the lease; the manager starts
66+
// inside the leader callback.
67+
mgr, err := ctrl.NewManager(kc, ctrl.Options{
68+
Scheme: agentScheme,
69+
LeaderElection: false,
70+
Metrics: metricsserver.Options{BindAddress: "0"},
71+
HealthProbeBindAddress: "",
72+
})
73+
if err != nil {
74+
logrus.Fatal(err)
75+
}
76+
77+
if err := (&agentconfig.ConfigReconciler{
78+
Client: mgr.GetClient(),
79+
Scheme: mgr.GetScheme(),
80+
SystemNamespace: namespace,
81+
}).SetupWithManager(mgr); err != nil {
82+
logrus.Fatal(err)
83+
}
84+
85+
go func() {
86+
if err := mgr.Start(ctx); err != nil {
87+
logrus.Fatal(err)
88+
}
89+
}()
90+
5091
appCtx, err := controllers.NewAppContext(clientConfig)
5192
if err != nil {
5293
logrus.Fatal(err)

0 commit comments

Comments
 (0)