Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions charts/cluster-api-runtime-extensions-nutanix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ A Helm chart for cluster-api-runtime-extensions-nutanix
| hooks.cni.cilium.crsStrategy.defaultCiliumConfigMap.name | string | `"cilium"` | |
| hooks.cni.cilium.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
| hooks.cni.cilium.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-cilium-cni-helm-values-template"` | |
| hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
| hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-multus-values-template"` | |
| hooks.cosi.controller.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
| hooks.cosi.controller.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-cosi-controller-helm-values-template"` | |
| hooks.csi.aws-ebs.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Multus daemon configuration overrides
daemonConfig:
readinessIndicatorFile: "{{ .SocketPath }}"

{{- if .SocketPath }}
# Volumes for CNI readiness socket
volumes:
- name: cni-readiness-sock
hostPath:
path: "{{ .SocketPath }}"
type: Socket

# Volume mounts for CNI readiness socket
volumeMounts:
- name: cni-readiness-sock
mountPath: "{{ .SocketPath }}"
readOnly: true
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2024 Nutanix. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

{{- if .Values.hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.create }}
apiVersion: v1
kind: ConfigMap
metadata:
name: '{{ .Values.hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.name }}'
data:
values.yaml: |-
{{- .Files.Get "addons/cni/multus/values-template.yaml" | nindent 4 }}
{{- end -}}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ data:
ChartName: metallb
ChartVersion: 0.15.2
RepositoryURL: '{{ if .Values.helmRepository.enabled }}oci://helm-repository.{{ .Release.Namespace }}.svc/charts{{ else }}https://metallb.github.io/metallb{{ end }}'
multus: |
ChartName: multus
ChartVersion: 0.1.0
RepositoryURL: '{{ if .Values.helmRepository.enabled }}oci://helm-repository.{{ .Release.Namespace }}.svc/charts{{ else }}https://mesosphere.github.io/charts/stable/{{ end }}'
nfd: |
ChartName: node-feature-discovery
ChartVersion: 0.18.1
Expand Down
21 changes: 21 additions & 0 deletions charts/cluster-api-runtime-extensions-nutanix/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,27 @@
}
}
}
},
"multus": {
"type": "object",
"properties": {
"helmAddonStrategy": {
"type": "object",
"properties": {
"defaultValueTemplateConfigMap": {
"type": "object",
"properties": {
"create": {
"type": "boolean"
},
"name": {
"type": "string"
}
}
}
}
}
}
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions charts/cluster-api-runtime-extensions-nutanix/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ hooks:
defaultValueTemplateConfigMap:
create: true
name: default-cilium-cni-helm-values-template
multus:
helmAddonStrategy:
defaultValueTemplateConfigMap:
create: true
name: default-multus-values-template
csi:
nutanix:
helmAddonStrategy:
Expand Down
5 changes: 5 additions & 0 deletions hack/addons/helm-chart-bundler/repos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ repositories:
charts:
metallb:
- 0.15.2
multus:
repoURL: https://mesosphere.github.io/charts/stable/
charts:
multus:
- 0.1.0
node-feature-discovery:
repoURL: https://kubernetes-sigs.github.io/node-feature-discovery/charts
charts:
Expand Down
20 changes: 20 additions & 0 deletions hack/addons/kustomize/multus/kustomization.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2024 Nutanix. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

metadata:
name: multus

sortOptions:
order: fifo

helmCharts:
- name: multus
namespace: kube-system
repo: https://mesosphere.github.io/charts/stable/
releaseName: multus
version: 0.1.0
includeCRDs: true
skipTests: true
24 changes: 24 additions & 0 deletions pkg/handlers/lifecycle/cni/multus/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2024 Nutanix. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// Package multus provides a standalone lifecycle handler for Multus CNI that automatically
// deploys Multus when:
// - The cluster is on a supported cloud provider (EKS or Nutanix)
// - A supported CNI provider is configured (Cilium or Calico)
//
// MultusHandler implements the cluster lifecycle hooks and:
// - Detects the cloud provider from the cluster infrastructure
// - Reads CNI configuration from cluster variables
// - Gets the socket path for the configured CNI (via cni.SocketPath)
// - Automatically deploys Multus with socket-based configuration
//
// helmAddonStrategy is the internal strategy that handles:
// - Templating Helm values with the CNI socket path
// - Deploying Multus using HelmAddon strategy with Go template-based values
//
// Multus relies on the readinessIndicatorFile configuration to wait for the primary CNI
// to be ready, eliminating the need for explicit wait logic in the strategy.
//
// This package does NOT expose Multus in the API - it's an internal addon
// that deploys automatically based on cloud provider and CNI selection.
package multus
190 changes: 190 additions & 0 deletions pkg/handlers/lifecycle/cni/multus/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright 2024 Nutanix. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package multus

import (
"context"
"fmt"

"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
commonhandlers "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/lifecycle"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
capiutils "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/utils"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/addons"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/cni"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/config"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options"
)

const (
defaultMultusReleaseName = "multus"
defaultMultusNamespace = metav1.NamespaceSystem
)

type MultusConfig struct {
*options.GlobalOptions

helmAddonConfig *addons.HelmAddonConfig
}

func NewMultusConfig(globalOptions *options.GlobalOptions) *MultusConfig {
return &MultusConfig{
GlobalOptions: globalOptions,
helmAddonConfig: addons.NewHelmAddonConfig(
"default-multus-values-template",
defaultMultusNamespace,
defaultMultusReleaseName,
),
}
}

func (m *MultusConfig) AddFlags(prefix string, flags *pflag.FlagSet) {
m.helmAddonConfig.AddFlags(prefix+".helm-addon", flags)
}

type MultusHandler struct {
client ctrlclient.Client
config *MultusConfig
helmChartInfoGetter *config.HelmChartGetter
}

var (
_ commonhandlers.Named = &MultusHandler{}
_ lifecycle.AfterControlPlaneInitialized = &MultusHandler{}
_ lifecycle.BeforeClusterUpgrade = &MultusHandler{}
)

func New(
c ctrlclient.Client,
cfg *MultusConfig,
helmChartInfoGetter *config.HelmChartGetter,
) *MultusHandler {
return &MultusHandler{
client: c,
config: cfg,
helmChartInfoGetter: helmChartInfoGetter,
}
}

func (m *MultusHandler) Name() string {
return "MultusHandler"
}

func (m *MultusHandler) AfterControlPlaneInitialized(
ctx context.Context,
req *runtimehooksv1.AfterControlPlaneInitializedRequest,
resp *runtimehooksv1.AfterControlPlaneInitializedResponse,
) {
commonResponse := &runtimehooksv1.CommonResponse{}
m.apply(ctx, &req.Cluster, commonResponse)
resp.Status = commonResponse.GetStatus()
resp.Message = commonResponse.GetMessage()
}

func (m *MultusHandler) BeforeClusterUpgrade(
ctx context.Context,
req *runtimehooksv1.BeforeClusterUpgradeRequest,
resp *runtimehooksv1.BeforeClusterUpgradeResponse,
) {
commonResponse := &runtimehooksv1.CommonResponse{}
m.apply(ctx, &req.Cluster, commonResponse)
resp.Status = commonResponse.GetStatus()
resp.Message = commonResponse.GetMessage()
}

func (m *MultusHandler) apply(
ctx context.Context,
cluster *clusterv1.Cluster,
resp *runtimehooksv1.CommonResponse,
) {
clusterKey := ctrlclient.ObjectKeyFromObject(cluster)

log := ctrl.LoggerFrom(ctx).WithValues(
"cluster",
clusterKey,
)

// Check if Multus is supported for this cloud provider
provider := capiutils.GetProvider(cluster)
if provider != "eks" && provider != "nutanix" {
log.V(5).Info(
"Multus is not supported for this cloud provider. Skipping Multus deployment.",
)
return
}

log.Info(fmt.Sprintf("Cluster is %s. Checking CNI configuration for Multus deployment.", provider))

// Read CNI configuration to detect which CNI is deployed
varMap := variables.ClusterVariablesToVariablesMap(cluster.Spec.Topology.Variables)

cniVar, err := variables.Get[v1alpha1.CNI](
varMap,
v1alpha1.ClusterConfigVariableName,
[]string{"addons", v1alpha1.CNIVariableName}...)
if err != nil {
if variables.IsNotFoundError(err) {
log.V(5).Info("No CNI specified in cluster config. Skipping Multus deployment.")
return
}
log.Error(err, "failed to read CNI configuration from cluster definition")
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
resp.SetMessage(fmt.Sprintf("failed to read CNI configuration: %v", err))
return
}

// Get socket path for the CNI provider
socketPath, err := cni.SocketPath(cniVar.Provider)
if err != nil {
log.V(5).
Info(fmt.Sprintf("Multus does not support CNI provider: %s. Skipping Multus deployment.", cniVar.Provider))
return
}

log.Info(fmt.Sprintf("Auto-deploying Multus for %s cluster with %s CNI", provider, cniVar.Provider))

// Get helm chart configuration
helmChart, err := m.helmChartInfoGetter.For(ctx, log, config.Multus)
if err != nil {
log.Error(
err,
"failed to get configmap with helm settings",
)
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
resp.SetMessage(
fmt.Sprintf("failed to get configuration to create helm addon: %v",
err,
),
)
return
}

// Create and apply helm addon using existing addons package
targetNamespace := m.config.DefaultsNamespace()
strategy := addons.NewHelmAddonApplier(
m.config.helmAddonConfig,
m.client,
helmChart,
).
WithValueTemplater(templateValuesFunc(socketPath)).
WithDefaultWaiter()

if err := strategy.Apply(ctx, cluster, targetNamespace, log); err != nil {
log.Error(err, "failed to deploy Multus")
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
resp.SetMessage(fmt.Sprintf("failed to deploy Multus: %v", err))
return
}

resp.SetStatus(runtimehooksv1.ResponseStatusSuccess)
resp.SetMessage("Multus deployed successfully")
}
39 changes: 39 additions & 0 deletions pkg/handlers/lifecycle/cni/multus/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Nutanix. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package multus

import (
"bytes"
"fmt"
"text/template"

clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)

// templateValuesFunc returns a template function that parses the Multus values template
// and replaces socket-related placeholders with the provided socket path.
func templateValuesFunc(socketPath string) func(*clusterv1.Cluster, string) (string, error) {
return func(_ *clusterv1.Cluster, valuesTemplate string) (string, error) {
t, err := template.New("").Parse(valuesTemplate)
if err != nil {
return "", fmt.Errorf("failed to parse Multus values template: %w", err)
}

type input struct {
SocketPath string
}

templateInput := input{
SocketPath: socketPath,
}

var b bytes.Buffer
err = t.Execute(&b, templateInput)
if err != nil {
return "", fmt.Errorf("failed to template Multus values: %w", err)
}

return b.String(), nil
}
}
Loading
Loading