diff --git a/e2e/multi-cluster/installation/agent_test.go b/e2e/multi-cluster/installation/agent_test.go index c2ae99c6d4..9ceca2093e 100644 --- a/e2e/multi-cluster/installation/agent_test.go +++ b/e2e/multi-cluster/installation/agent_test.go @@ -37,7 +37,7 @@ var _ = Describe("Fleet installation with TLS agent modes", func() { "--type=merge", "-p", fmt.Sprintf( - `{"data":{"config":"{\"apiServerURL\": \"https://google.com\", \"apiServerCA\": \"\", \"agentTLSMode\": \"%s\"}"}}`, + `{"data":{"config":"{\"apiServerURL\": \"https://google.com\", \"apiServerCA\": \"\", \"agentTLSMode\": \"%s\", \"agentCheckinInterval\": \"15m\"}"}}`, // agent check-in interval must be non-zero agentMode, ), ) diff --git a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go index 829fecdd82..2ab872b57a 100644 --- a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go +++ b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go @@ -357,6 +357,10 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust apiServerCA = secret.Data[config.APIServerCAKey] ) + if cfg.AgentCheckinInterval.Seconds() <= 0 { + return status, errors.New("agent check-in interval cannot be 0 or less") + } + if apiServerURL == "" { if len(cfg.APIServerURL) == 0 { // Current config cannot be deployed, so remove the "config changed" mark diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go index 3f65f61925..18f11dd535 100644 --- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go +++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go @@ -11,6 +11,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "sort" @@ -335,6 +336,10 @@ func (h *handler) newAgentBundle(ns string, cluster *fleet.Cluster) ([]runtime.O priorityClassName = scheduling.FleetAgentPriorityClassName } + if cfg.AgentCheckinInterval.Seconds() <= 0 { + return nil, errors.New("agent check-in interval cannot be 0 or less") + } + if cluster.Spec.AgentTolerations != nil { sortTolerations(cluster.Spec.AgentTolerations) } diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go index 9a6eb7a05d..236d2f6fe2 100644 --- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go +++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go @@ -1,10 +1,12 @@ package manageagent import ( + "fmt" "maps" "reflect" "strings" "testing" + "time" "github.com/rancher/wrangler/v3/pkg/generic/fake" "github.com/rancher/wrangler/v3/pkg/schemes" @@ -14,14 +16,51 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "github.com/rancher/fleet/internal/config" fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" appsv1 "k8s.io/api/apps/v1" networkv1 "k8s.io/api/networking/v1" "sigs.k8s.io/yaml" - - "github.com/rancher/fleet/internal/config" ) +func TestNewAgentBundle(t *testing.T) { + testCases := []struct { + agentCheckInInterval time.Duration + }{ + { + 0 * time.Second, + }, + { + -5 * time.Second, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("agent check-in interval of %s", tc.agentCheckInInterval), func(t *testing.T) { + config.Set(&config.Config{ + AgentCheckinInterval: metav1.Duration{Duration: tc.agentCheckInInterval}, + }) + + // ensure leader election env is set so NewLeaderElectionOptionsWithPrefix doesn't error + t.Setenv("FLEET_AGENT_ELECTION_LEASE_DURATION", "15s") + t.Setenv("FLEET_AGENT_ELECTION_RENEW_DEADLINE", "10s") + t.Setenv("FLEET_AGENT_ELECTION_RETRY_PERIOD", "2s") + + h := handler{systemNamespace: "blah"} + obj, err := h.newAgentBundle("foo", &fleet.Cluster{Spec: fleet.ClusterSpec{AgentNamespace: "bar"}}) + + if obj != nil { + t.Fatalf("expected obj returned by newAgentBundle to be nil") + } + + expectedStr := "interval cannot be 0 or less" + if err == nil || !strings.Contains(err.Error(), expectedStr) { + t.Fatalf("expected error %v returned by newAgentBundle to contain %q", err, expectedStr) + } + }) + } +} + func TestOnClusterChangeAffinity(t *testing.T) { ctrl := gomock.NewController(t) namespaces := fake.NewMockNonNamespacedControllerInterface[*corev1.Namespace, *corev1.NamespaceList](ctrl) @@ -119,7 +158,9 @@ func TestOnClusterChangeAffinity(t *testing.T) { func TestNewAgentBundle_SortsAgentTolerations(t *testing.T) { // make sure config is set for newAgentBundle - config.Set(config.DefaultConfig()) + cfg := config.DefaultConfig() + cfg.AgentCheckinInterval = metav1.Duration{Duration: 1 * time.Second} // non-zero to prevent errors covered elsewhere. + config.Set(cfg) checkRegisterAddToScheme(t, appsv1.AddToScheme) checkRegisterAddToScheme(t, networkv1.AddToScheme) @@ -269,6 +310,8 @@ func TestNewAgentBundle_PropagatesAgentImagePullSecrets(t *testing.T) { if tc.configPullSecrets != nil { cfg.ImagePullSecrets = *tc.configPullSecrets } + + cfg.AgentCheckinInterval = metav1.Duration{Duration: 1 * time.Second} // non-zero to prevent errors covered elsewhere. config.Set(cfg) checkRegisterAddToScheme(t, appsv1.AddToScheme)