multus-cni/pkg/server/config/generator.go
dougbtv 528d4f150c Functionality for Aux CNI Chain using subdirectory based CNI configuration loading.
Removes the it `fails to execute confListDel given no 'plugins' key"` test.

This test no longer fails after libcni version 1.2.3.
It probably shouldn't failduring a DEL action as it is, we want the least error prone path.

The GC test now uses both cni.dev attachment formats.

Uses both attachment formats as per https://github.com/containernetworking/cni/issues/1101 for GC's cni.dev/valid-attachments & cni.dev/attachments
2025-04-15 15:53:00 -04:00

220 lines
7.1 KiB
Go

// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/blang/semver"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
)
const (
configListCapabilityKey = "plugins"
multusPluginName = "multus-shim"
singleConfigCapabilityKey = "capabilities"
)
// Option mutates the `conf` object
type Option func(conf *MultusConf) error
// MultusConf holds the multus configuration
type MultusConf struct {
BinDir string `json:"binDir,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
CNIVersion string `json:"cniVersion"`
LogFile string `json:"logFile,omitempty"`
LogLevel string `json:"logLevel,omitempty"`
LogToStderr bool `json:"logToStderr,omitempty"`
LogOptions *logging.LogOptions `json:"logOptions,omitempty"`
Name string `json:"name"`
ClusterNetwork string `json:"clusterNetwork,omitempty"`
NamespaceIsolation bool `json:"namespaceIsolation,omitempty"`
RawNonIsolatedNamespaces string `json:"globalNamespaces,omitempty"`
ReadinessIndicatorFile string `json:"readinessindicatorfile,omitempty"`
Type string `json:"type"`
CniDir string `json:"cniDir,omitempty"`
CniConfigDir string `json:"cniConfigDir,omitempty"`
AuxiliaryCNIChainName string `json:"auxiliaryCNIChainName,omitempty"`
DaemonSocketDir string `json:"daemonSocketDir,omitempty"`
MultusConfigFile string `json:"multusConfigFile,omitempty"`
MultusMasterCni string `json:"multusMasterCNI,omitempty"`
MultusAutoconfigDir string `json:"multusAutoconfigDir,omitempty"`
ForceCNIVersion bool `json:"forceCNIVersion,omitempty"`
OverrideNetworkName bool `json:"overrideNetworkName,omitempty"`
}
// ParseMultusConfig parses multus config from configPath and create MultusConf.
func ParseMultusConfig(configPath string) (*MultusConf, error) {
config, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("ParseMultusConfig failed to read the config file's contents: %w", err)
}
multusconf := MultusConf{
MultusConfigFile: "auto",
Type: multusPluginName,
Capabilities: map[string]bool{},
CniConfigDir: "/etc/cni/net.d",
}
if err := json.Unmarshal(config, &multusconf); err != nil {
return nil, fmt.Errorf("failed to unmarshall the daemon configuration: %w", err)
}
multusconf.Name = MultusDefaultNetworkName // change name
return &multusconf, nil
}
// CheckVersionCompatibility checks compatibilty of the
// top level cni version with the delegate cni version.
// Since version 0.4.0, CHECK was introduced, which
// causes incompatibility.
func CheckVersionCompatibility(mc *MultusConf, delegate interface{}) error {
const versionFmt = "delegate cni version is %s while top level cni version is %s"
v040, _ := semver.Make("0.4.0")
multusCNIVersion, err := semver.Make(mc.CNIVersion)
if err != nil {
return errors.New("couldn't get top level cni version")
}
if multusCNIVersion.GTE(v040) {
delegatesMap, ok := delegate.(map[string]interface{})
if !ok {
return errors.New("couldn't get cni version of delegate")
}
delegateVersion, ok := delegatesMap["cniVersion"].(string)
if !ok {
return errors.New("couldn't get cni version of delegate")
}
v, err := semver.Make(delegateVersion)
if err != nil {
return err
}
if v.LT(v040) {
return fmt.Errorf(versionFmt, delegateVersion, mc.CNIVersion)
}
}
return nil
}
// Generate generates the multus configuration from whatever state is currently
// held
func (mc *MultusConf) Generate() (string, error) {
// before marshal, flush variables which is not required for multus-shim config
mc.CniConfigDir = ""
mc.MultusConfigFile = ""
mc.MultusAutoconfigDir = ""
mc.MultusMasterCni = ""
mc.ForceCNIVersion = false
// Readiness indicator file existence is already handled by the
// ConfigManager via an fsnotify watch, so CmdAdd/CmdDel don't need to.
mc.ReadinessIndicatorFile = ""
data, err := json.Marshal(mc)
return string(data), err
}
func (mc *MultusConf) setCapabilities(cniData interface{}) error {
var enabledCapabilities []string
var pluginsList []interface{}
cniDataMap, ok := cniData.(map[string]interface{})
if ok {
if pluginsListEntry, ok := cniDataMap[configListCapabilityKey]; ok {
pluginsList = pluginsListEntry.([]interface{})
}
} else {
return errors.New("couldn't get cni config from delegate")
}
if len(pluginsList) > 0 {
for _, pluginData := range pluginsList {
enabledCapabilities = append(
enabledCapabilities,
extractCapabilities(pluginData)...)
}
} else {
enabledCapabilities = extractCapabilities(cniData)
}
for _, capability := range enabledCapabilities {
mc.Capabilities[capability] = true
}
return nil
}
func extractCapabilities(capabilitiesInterface interface{}) []string {
capabilitiesMap, ok := capabilitiesInterface.(map[string]interface{})
if !ok {
return nil
}
capabilitiesMapEntry, ok := capabilitiesMap[singleConfigCapabilityKey]
if !ok {
return nil
}
capabilities, ok := capabilitiesMapEntry.(map[string]interface{})
if !ok {
return nil
}
var enabledCapabilities []string
if len(capabilities) > 0 {
for capName, isCapabilityEnabled := range capabilities {
if isCapabilityEnabled.(bool) {
enabledCapabilities = append(enabledCapabilities, capName)
}
}
}
return enabledCapabilities
}
func findMasterPlugin(cniConfigDirPath string, remainingTries int) (string, error) {
if remainingTries == 0 {
return "", fmt.Errorf("could not find a plugin configuration in %s", cniConfigDirPath)
}
var cniPluginConfigs []string
files, err := os.ReadDir(cniConfigDirPath)
if err != nil {
return "", fmt.Errorf("error when listing the CNI plugin configurations: %w", err)
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "00-multus") {
continue
}
fileExtension := filepath.Ext(file.Name())
if fileExtension == ".conf" || fileExtension == ".conflist" {
cniPluginConfigs = append(cniPluginConfigs, file.Name())
}
}
if len(cniPluginConfigs) == 0 {
time.Sleep(time.Second)
return findMasterPlugin(cniConfigDirPath, remainingTries-1)
}
sort.Strings(cniPluginConfigs)
return cniPluginConfigs[0], nil
}