forked from github/multus-cni
* build: install the multus binary in an init container Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * build: generate kubeconfig via go Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * build: generate multus cni configuration via golang Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * build: provide a docker img for daemon based deployments We will have 2 different images (only on amd64 archs): - legacy entrypoint script based - daemonized process The `image-build` docker action is updated, to build these 2 images. There will be 2 different deployment specs, along with e2e test lanes, one for each of the aforementioned alternatives. Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * build: delegate CNI config watch loop via golang For the thick-plugin alternative, provide the watch loop for configuration regeneration via a golang binary. Over time, this binary is expected to run the control loop to watch out for pod updates. To enable current multus users to chose when they upgrade to this new deployment setup, these changes are provided in separate multus images, having a different yaml spec files. Both of these alternatives are tested e2e, since a new lane is introduced. The following libraries are introduced, along with the motivation for adding them: - dproxy: allows traversing the default network configuration arbitrarily, similar to what an X path / JSON path tool provides. Repo is available at [0]. - fsnotify: watch for changes in the default CNI configuration file. Repo is available at [1]. The config map providing the default network CNI configuration is not copied over, since originally, the user was not required to install a default network CNI plugin first, but, nowadays, this is a required step of multus. As such, it is no longer required to provide a default CNI configuration. [0] - https://github.com/koron/go-dproxy [1] - https://github.com/fsnotify/fsnotify Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * run gofmt Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * refactor: make the builder pattern more idiomatic to golang Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com> * build: update github actions to release new imgs Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
234 lines
7.9 KiB
Go
234 lines
7.9 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"
|
|
"fmt"
|
|
"io/ioutil"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/koron/go-dproxy"
|
|
|
|
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
|
|
)
|
|
|
|
// MultusDefaultNetworkName holds the default name of the multus network
|
|
const (
|
|
multusConfigFileName = "00-multus.conf"
|
|
MultusDefaultNetworkName = "multus-cni-network"
|
|
userRWPermission = 0600
|
|
)
|
|
|
|
// Manager monitors the configuration of the primary CNI plugin, and
|
|
// regenerates multus configuration whenever it gets updated.
|
|
type Manager struct {
|
|
cniConfigData dproxy.Proxy
|
|
configWatcher *fsnotify.Watcher
|
|
multusConfig *MultusConf
|
|
multusConfigDir string
|
|
multusConfigFilePath string
|
|
primaryCNIConfigPath string
|
|
}
|
|
|
|
// NewManager returns a config manager object, configured to persist the
|
|
// configuration to `multusAutoconfigDir`. This constructor will auto-discover
|
|
// the primary CNI for which it will delegate.
|
|
func NewManager(config MultusConf, multusAutoconfigDir string) (*Manager, error) {
|
|
defaultCNIPluginName, err := primaryCNIPluginName(multusAutoconfigDir)
|
|
if err != nil {
|
|
_ = logging.Errorf("failed to find the primary CNI plugin: %v", err)
|
|
return nil, err
|
|
}
|
|
return newManager(config, multusAutoconfigDir, defaultCNIPluginName)
|
|
}
|
|
|
|
// NewManagerWithExplicitPrimaryCNIPlugin returns a config manager object,
|
|
// configured to persist the configuration to `multusAutoconfigDir`. This
|
|
// constructor will use the primary CNI plugin indicated by the user, via the
|
|
// primaryCNIPluginName variable.
|
|
func NewManagerWithExplicitPrimaryCNIPlugin(config MultusConf, multusAutoconfigDir string, primaryCNIPluginName string) (*Manager, error) {
|
|
return newManager(config, multusAutoconfigDir, primaryCNIPluginName)
|
|
}
|
|
|
|
func newManager(config MultusConf, multusConfigDir string, defaultCNIPluginName string) (*Manager, error) {
|
|
watcher, err := newWatcher(multusConfigDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configManager := &Manager{
|
|
configWatcher: watcher,
|
|
multusConfig: &config,
|
|
multusConfigDir: multusConfigDir,
|
|
multusConfigFilePath: cniPluginConfigFilePath(multusConfigDir, multusConfigFileName),
|
|
primaryCNIConfigPath: cniPluginConfigFilePath(multusConfigDir, defaultCNIPluginName),
|
|
}
|
|
|
|
if err := configManager.loadPrimaryCNIConfigFromFile(); err != nil {
|
|
return nil, fmt.Errorf("failed to load the primary CNI configuration as a multus delegate")
|
|
}
|
|
return configManager, nil
|
|
}
|
|
|
|
func (m *Manager) loadPrimaryCNIConfigFromFile() error {
|
|
primaryCNIConfigData, err := primaryCNIData(m.primaryCNIConfigPath)
|
|
if err != nil {
|
|
return logging.Errorf("failed to access the primary CNI configuration from %s: %v", m.primaryCNIConfigPath, err)
|
|
}
|
|
m.loadPrimaryCNIConfigurationData(primaryCNIConfigData)
|
|
return nil
|
|
}
|
|
|
|
// OverrideNetworkName overrides the name of the multus configuration with the
|
|
// name of the delegated primary CNI.
|
|
func (m *Manager) OverrideNetworkName() error {
|
|
networkName, err := m.cniConfigData.M("name").String()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to access delegate CNI plugin name")
|
|
}
|
|
if networkName == "" {
|
|
return fmt.Errorf("the primary CNI Configuration does not feature the network name: %v", m.cniConfigData)
|
|
}
|
|
m.multusConfig.Mutate(WithOverriddenName(networkName))
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) loadPrimaryCNIConfigurationData(primaryCNIConfigData interface{}) {
|
|
cniConfig := dproxy.New(primaryCNIConfigData)
|
|
m.cniConfigData = cniConfig
|
|
m.multusConfig.Mutate(
|
|
withDelegates(primaryCNIConfigData),
|
|
withCapabilities(cniConfig))
|
|
}
|
|
|
|
// GenerateConfig generates a multus configuration from its current state
|
|
func (m Manager) GenerateConfig() (string, error) {
|
|
if err := m.loadPrimaryCNIConfigFromFile(); err != nil {
|
|
_ = logging.Errorf("failed to read the primary CNI plugin config from %s", m.primaryCNIConfigPath)
|
|
return "", nil
|
|
}
|
|
return m.multusConfig.Generate()
|
|
}
|
|
|
|
// MonitorDelegatedPluginConfiguration monitors the configuration file pointed
|
|
// to by the primaryCNIPluginName attribute, and re-generates the multus
|
|
// configuration whenever the primary CNI config is updated.
|
|
func (m Manager) MonitorDelegatedPluginConfiguration(shutDown chan struct{}, done chan struct{}) error {
|
|
logging.Verbosef("started to watch file %s", m.primaryCNIConfigPath)
|
|
|
|
for {
|
|
select {
|
|
case event := <-m.configWatcher.Events:
|
|
// we're watching the DIR where the config sits, and the event
|
|
// does not concern the primary CNI config. Skip it.
|
|
if event.Name != m.primaryCNIConfigPath {
|
|
logging.Debugf("skipping un-related event %v", event)
|
|
continue
|
|
}
|
|
|
|
if !shouldRegenerateConfig(event) {
|
|
continue
|
|
}
|
|
|
|
updatedConfig, err := m.GenerateConfig()
|
|
if err != nil {
|
|
_ = logging.Errorf("failed to regenerate the multus configuration: %v", err)
|
|
}
|
|
|
|
logging.Debugf("Re-generated MultusCNI config: %s", updatedConfig)
|
|
if err := m.PersistMultusConfig(updatedConfig); err != nil {
|
|
_ = logging.Errorf("failed to persist the multus configuration: %v", err)
|
|
}
|
|
|
|
case err := <-m.configWatcher.Errors:
|
|
if err == nil {
|
|
continue
|
|
}
|
|
logging.Errorf("CNI monitoring error %v", err)
|
|
|
|
case <-shutDown:
|
|
logging.Verbosef("Stopped monitoring, closing channel ...")
|
|
_ = m.configWatcher.Close()
|
|
done <- struct{}{}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// PersistMultusConfig persists the provided configuration to the disc, with
|
|
// Read / Write permissions. The output file path is `<multus auto config dir>/00-multus.conf`
|
|
func (m Manager) PersistMultusConfig(config string) error {
|
|
return ioutil.WriteFile(m.multusConfigFilePath, []byte(config), userRWPermission)
|
|
}
|
|
|
|
func primaryCNIPluginName(multusAutoconfigDir string) (string, error) {
|
|
masterCniConfigFileName, err := findMasterPlugin(multusAutoconfigDir, 120)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to find the cluster master CNI plugin: %w", err)
|
|
}
|
|
return masterCniConfigFileName, nil
|
|
}
|
|
|
|
func cniPluginConfigFilePath(cniConfigDir string, cniConfigFileName string) string {
|
|
return cniConfigDir + fmt.Sprintf("/%s", cniConfigFileName)
|
|
}
|
|
|
|
func newWatcher(cniConfigDir string) (*fsnotify.Watcher, error) {
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create new watcher for %q: %v", cniConfigDir, err)
|
|
}
|
|
defer func() {
|
|
// Close watcher on error
|
|
if err != nil {
|
|
watcher.Close()
|
|
}
|
|
}()
|
|
|
|
if err = watcher.Add(cniConfigDir); err != nil {
|
|
return nil, fmt.Errorf("failed to add watch on %q: %v", cniConfigDir, err)
|
|
}
|
|
|
|
return watcher, nil
|
|
}
|
|
|
|
func shouldRegenerateConfig(event fsnotify.Event) bool {
|
|
return event.Op&fsnotify.Write == fsnotify.Write ||
|
|
event.Op&fsnotify.Create == fsnotify.Create
|
|
}
|
|
|
|
func primaryCNIData(masterCNIPluginPath string) (interface{}, error) {
|
|
masterCNIConfigData, err := ioutil.ReadFile(masterCNIPluginPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read the cluster primary CNI config %s: %w", masterCNIPluginPath, err)
|
|
}
|
|
|
|
var cniData interface{}
|
|
if err := json.Unmarshal(masterCNIConfigData, &cniData); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshall primary CNI config: %w", err)
|
|
}
|
|
return cniData, nil
|
|
}
|
|
|
|
func documentProxy(masterCNIConfigData []byte) (dproxy.Proxy, error) {
|
|
var cniData interface{}
|
|
if err := json.Unmarshal(masterCNIConfigData, &cniData); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshall the delegate CNI configuration: %w", err)
|
|
}
|
|
return dproxy.New(cniData), nil
|
|
}
|