Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: "{{ .ReadinessSocketPath }}"

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

# Volume mounts for CNI readiness socket
volumeMounts:
- name: cni-readiness-sock
mountPath: "{{ .ReadinessSocketPath }}"
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 EKS cloud provider
// - 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 readiness socket path for the configured CNI (via cni.ReadinessSocketPath)
// - 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
181 changes: 181 additions & 0 deletions pkg/handlers/lifecycle/cni/multus/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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/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" {
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
}

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(cniVar)).
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")
}
49 changes: 49 additions & 0 deletions pkg/handlers/lifecycle/cni/multus/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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"

"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/cni"
)

// templateValuesFunc returns a template function that parses the Multus values template
// and replaces socket-related placeholders with the readiness socket path.
// It looks up the socket path internally based on the CNI provider.
func templateValuesFunc(cniVar v1alpha1.CNI) func(*clusterv1.Cluster, string) (string, error) {
return func(_ *clusterv1.Cluster, valuesTemplate string) (string, error) {
// Look up the readiness socket path for the CNI provider
readinessSocketPath, err := cni.ReadinessSocketPath(cniVar.Provider)
if err != nil {
return "", fmt.Errorf("failed to get readiness socket path for CNI provider %s: %w", cniVar.Provider, err)
}

t, err := template.New("").Parse(valuesTemplate)
if err != nil {
return "", fmt.Errorf("failed to parse Multus values template: %w", err)
}

type input struct {
ReadinessSocketPath string
}

templateInput := input{
ReadinessSocketPath: readinessSocketPath,
}

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