diff --git a/charts/fleet-agent/templates/deployment.yaml b/charts/fleet-agent/templates/deployment.yaml index a9033b2892..a13d85abee 100644 --- a/charts/fleet-agent/templates/deployment.yaml +++ b/charts/fleet-agent/templates/deployment.yaml @@ -89,6 +89,9 @@ spec: - name: kube emptyDir: {} serviceAccountName: fleet-agent + {{- if .Values.fleetAgent.hostNetwork }} + hostNetwork: true + {{- end }} nodeSelector: {{ include "linux-node-selector" . | nindent 8 }} {{- if .Values.fleetAgent.nodeSelector }} {{ toYaml .Values.fleetAgent.nodeSelector | indent 8 }} diff --git a/charts/fleet-agent/values.yaml b/charts/fleet-agent/values.yaml index cca576418d..201016ec72 100644 --- a/charts/fleet-agent/values.yaml +++ b/charts/fleet-agent/values.yaml @@ -44,6 +44,9 @@ fleetAgent: nodeSelector: {} ## List of node taints to tolerate (requires Kubernetes >= 1.6) tolerations: [] + ## HostNetwork setting for the agent deployment. + ## When set allows for provisioning of network related bundles (CNI configuration) in a cluster without CNI. + hostNetwork: false kubectl: ## Node labels for pod assignment ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ diff --git a/charts/fleet-crd/templates/crds.yaml b/charts/fleet-crd/templates/crds.yaml index 1f3776b13a..f20173ca36 100644 --- a/charts/fleet-crd/templates/crds.yaml +++ b/charts/fleet-crd/templates/crds.yaml @@ -5440,6 +5440,13 @@ spec: either be predefined, or generated when importing the cluster.' nullable: true type: string + hostNetwork: + description: 'HostNetwork sets the agent StatefulSet to use hostNetwork: + true setting. + + Allows for provisioning of network related bundles (CNI configuration).' + nullable: true + type: boolean kubeConfigSecret: description: 'KubeConfigSecret is the name of the secret containing the kubeconfig for the downstream cluster. @@ -5522,6 +5529,11 @@ spec: used to detect changes. nullable: true type: string + agentHostNetwork: + description: AgentHostNetwork defines observed state of spec.hostNetwork + setting that is currently used. + nullable: true + type: boolean agentMigrated: description: 'AgentMigrated is always set to true after importing a cluster. If diff --git a/internal/cmd/controller/agentmanagement/agent/manifest.go b/internal/cmd/controller/agentmanagement/agent/manifest.go index 8de1c097ad..b8bb72822d 100644 --- a/internal/cmd/controller/agentmanagement/agent/manifest.go +++ b/internal/cmd/controller/agentmanagement/agent/manifest.go @@ -37,6 +37,7 @@ type ManifestOptions struct { SystemDefaultRegistry string AgentAffinity *corev1.Affinity AgentResources *corev1.ResourceRequirements + HostNetwork bool } // Manifest builds and returns a deployment manifest for the fleet-agent with a @@ -298,6 +299,9 @@ func agentApp(namespace string, agentScope string, opts ManifestOptions) *appsv1 // additional tolerations from cluster app.Spec.Template.Spec.Tolerations = append(app.Spec.Template.Spec.Tolerations, opts.AgentTolerations...) + // Set hostNetwork + app.Spec.Template.Spec.HostNetwork = opts.HostNetwork + // overwrite affinity if present on cluster if opts.AgentAffinity != nil { app.Spec.Template.Spec.Affinity = opts.AgentAffinity diff --git a/internal/cmd/controller/agentmanagement/agent/manifest_test.go b/internal/cmd/controller/agentmanagement/agent/manifest_test.go index 1aab99104d..e4497c2fab 100644 --- a/internal/cmd/controller/agentmanagement/agent/manifest_test.go +++ b/internal/cmd/controller/agentmanagement/agent/manifest_test.go @@ -104,6 +104,54 @@ func TestManifestAgentTolerations(t *testing.T) { } } +func TestManifestAgentHostNetwork(t *testing.T) { + const namespace = "fleet-system" + const scope = "test-scope" + baseOpts := ManifestOptions{ + AgentEnvVars: []corev1.EnvVar{}, + AgentImage: "rancher/fleet:1.2.3", + AgentImagePullPolicy: "Always", + AgentTolerations: []corev1.Toleration{}, + CheckinInterval: "1s", + PrivateRepoURL: "private.rancher.com:5000", + SystemDefaultRegistry: "default.rancher.com", + } + + for _, testCase := range []struct { + name string + getOpts func() ManifestOptions + expectedNetwork bool + }{ + { + name: "DefaultSetting", + getOpts: func() ManifestOptions { + return baseOpts + }, + expectedNetwork: false, + }, + { + name: "With hostNetwork", + getOpts: func() ManifestOptions { + withHostNetwork := baseOpts + withHostNetwork.HostNetwork = true + return withHostNetwork + }, + expectedNetwork: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + agent := getAgentFromManifests(namespace, scope, testCase.getOpts()) + if agent == nil { + t.Fatal("there were no deployments returned from the manifests") + } + + if !cmp.Equal(agent.Spec.Template.Spec.HostNetwork, testCase.expectedNetwork) { + t.Fatalf("hostNetwork is not as expected: %v", agent.Spec.Template.Spec.HostNetwork) + } + }) + } +} + func TestManifestAgentAffinity(t *testing.T) { const namespace = "fleet-system" diff --git a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go index ecfe90787d..4297fbb28a 100644 --- a/internal/cmd/controller/agentmanagement/controllers/cluster/import.go +++ b/internal/cmd/controller/agentmanagement/controllers/cluster/import.go @@ -1,6 +1,7 @@ package cluster import ( + "cmp" "context" "crypto/sha256" "encoding/json" @@ -32,6 +33,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "k8s.io/utils/ptr" ) var ( @@ -323,6 +325,7 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust PrivateRepoURL: cluster.Spec.PrivateRepoURL, AgentAffinity: cluster.Spec.AgentAffinity, AgentResources: cluster.Spec.AgentResources, + HostNetwork: *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)), }, }) if err != nil { diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go index 095dbf2817..e6c3b838fe 100644 --- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go +++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent.go @@ -6,6 +6,7 @@ package manageagent import ( + "cmp" "context" "crypto/sha256" "encoding/json" @@ -28,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ) const ( @@ -169,6 +171,11 @@ func (h *handler) updateClusterStatus(cluster *fleet.Cluster, status fleet.Clust changed = true } + if hostNetwork := *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)); status.AgentHostNetwork != hostNetwork { + status.AgentHostNetwork = hostNetwork + changed = true + } + if c, hash, err := hashChanged(cluster.Spec.AgentAffinity, status.AgentAffinityHash); err != nil { return status, changed, err } else if c { @@ -263,6 +270,7 @@ func (h *handler) newAgentBundle(ns string, cluster *fleet.Cluster) (runtime.Obj SystemDefaultRegistry: cfg.SystemDefaultRegistry, AgentAffinity: cluster.Spec.AgentAffinity, AgentResources: cluster.Spec.AgentResources, + HostNetwork: *cmp.Or(cluster.Spec.HostNetwork, ptr.To(false)), }, ) agentYAML, err := yaml.Export(objs...) diff --git a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go index b865be9b2b..0bca795e69 100644 --- a/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go +++ b/internal/cmd/controller/agentmanagement/controllers/manageagent/manageagent_test.go @@ -7,6 +7,7 @@ import ( "github.com/rancher/wrangler/v3/pkg/generic/fake" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/utils/ptr" fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" ) @@ -271,3 +272,59 @@ func TestOnClusterChangeTolerations(t *testing.T) { }) } } + +func TestOnClusterChangeHostNetwork(t *testing.T) { + ctrl := gomock.NewController(t) + namespaces := fake.NewMockNonNamespacedControllerInterface[*corev1.Namespace, *corev1.NamespaceList](ctrl) + h := &handler{namespaces: namespaces} + + for _, tt := range []struct { + name string + cluster *fleet.Cluster + status fleet.ClusterStatus + expectedStatus fleet.ClusterStatus + enqueues int + }{ + { + name: "Empty", + cluster: &fleet.Cluster{}, + status: fleet.ClusterStatus{}, + expectedStatus: fleet.ClusterStatus{}, + enqueues: 0, + }, + { + name: "Equal HostNetwork", + cluster: &fleet.Cluster{Spec: fleet.ClusterSpec{HostNetwork: ptr.To(true)}}, + status: fleet.ClusterStatus{AgentHostNetwork: true}, + expectedStatus: fleet.ClusterStatus{AgentHostNetwork: true}, + enqueues: 0, + }, + { + name: "Changed HostNetwork", + cluster: &fleet.Cluster{Spec: fleet.ClusterSpec{HostNetwork: ptr.To(true)}}, + status: fleet.ClusterStatus{AgentHostNetwork: false}, + expectedStatus: fleet.ClusterStatus{AgentHostNetwork: true}, + enqueues: 1, + }, + { + name: "Removed Resources", + cluster: &fleet.Cluster{Spec: fleet.ClusterSpec{}}, + status: fleet.ClusterStatus{AgentHostNetwork: true}, + expectedStatus: fleet.ClusterStatus{AgentHostNetwork: false}, + enqueues: 1, + }, + } { + t.Run(tt.name, func(t *testing.T) { + namespaces.EXPECT().Enqueue(gomock.Any()).Times(tt.enqueues) + + status, err := h.onClusterStatusChange(tt.cluster, tt.status) + if err != nil { + t.Error(err) + } + + if status.AgentHostNetwork != tt.expectedStatus.AgentHostNetwork { + t.Fatalf("agent hostStatus is not equal: %v vs %v", status.AgentHostNetwork, tt.expectedStatus.AgentHostNetwork) + } + }) + } +} diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/cluster_types.go b/pkg/apis/fleet.cattle.io/v1alpha1/cluster_types.go index d50d7ecf3b..dc1ed40c76 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/cluster_types.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/cluster_types.go @@ -125,6 +125,12 @@ type ClusterSpec struct { // +nullable // AgentResources sets the resources for the cluster's agent deployment. AgentResources *corev1.ResourceRequirements `json:"agentResources,omitempty"` + + // +nullable + // +optional + // HostNetwork sets the agent StatefulSet to use hostNetwork: true setting. + // Allows for provisioning of network related bundles (CNI configuration). + HostNetwork *bool `json:"hostNetwork,omitempty"` } type ClusterStatus struct { @@ -154,6 +160,9 @@ type ClusterStatus struct { // AgentPrivateRepoURL is the private repo URL for the agent that is currently used. // +nullable AgentPrivateRepoURL string `json:"agentPrivateRepoURL,omitempty"` + // AgentHostNetwork defines observed state of spec.hostNetwork setting that is currently used. + // +nullable + AgentHostNetwork bool `json:"agentHostNetwork,omitempty"` // AgentDeployedGeneration is the generation of the agent that is currently deployed. // +nullable AgentDeployedGeneration *int64 `json:"agentDeployedGeneration,omitempty"` diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go index 8ae8fbfb5a..e64aa50838 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go @@ -1059,6 +1059,11 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = new(corev1.ResourceRequirements) (*in).DeepCopyInto(*out) } + if in.HostNetwork != nil { + in, out := &in.HostNetwork, &out.HostNetwork + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.