cni/plugin.go (165 lines of code) (raw):
// Copyright 2017 Microsoft. All rights reserved.
// MIT License
package cni
import (
"context"
"fmt"
"os"
"runtime"
"time"
"github.com/Azure/azure-container-networking/cni/log"
"github.com/Azure/azure-container-networking/common"
"github.com/Azure/azure-container-networking/platform"
"github.com/Azure/azure-container-networking/processlock"
"github.com/Azure/azure-container-networking/store"
cniInvoke "github.com/containernetworking/cni/pkg/invoke"
cniSkel "github.com/containernetworking/cni/pkg/skel"
cniTypes "github.com/containernetworking/cni/pkg/types"
cniTypesCurr "github.com/containernetworking/cni/pkg/types/100"
cniVers "github.com/containernetworking/cni/pkg/version"
"github.com/pkg/errors"
"go.uber.org/zap"
)
var (
logger = log.CNILogger.With(zap.String("component", "cni-plugin"))
storeLogger = log.CNILogger.With(zap.String("component", "cni-store"))
)
var errEmptyContent = errors.New("read content is zero bytes")
// Plugin is the parent class for CNI plugins.
type Plugin struct {
*common.Plugin
version string
}
// NewPlugin creates a new CNI plugin.
func NewPlugin(name, version string) (*Plugin, error) {
// Setup base plugin.
plugin, err := common.NewPlugin(name, version)
if err != nil {
return nil, err
}
return &Plugin{
Plugin: plugin,
version: version,
}, nil
}
// Initialize initializes the plugin.
func (plugin *Plugin) Initialize(config *common.PluginConfig) error {
// Initialize the base plugin.
plugin.Plugin.Initialize(config)
return nil
}
// Uninitialize uninitializes the plugin.
func (plugin *Plugin) Uninitialize() {
plugin.Plugin.Uninitialize()
}
// Execute executes the CNI command.
func (plugin *Plugin) Execute(api PluginApi) (err error) {
// Recover from panics and convert them to CNI errors.
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<12)
len := runtime.Stack(buf, false)
cniErr := &cniTypes.Error{
Code: ErrRuntime,
Msg: fmt.Sprintf("%v", r),
Details: string(buf[:len]),
}
cniErr.Print()
err = cniErr
logger.Info("Recovered panic",
zap.String("error", cniErr.Msg),
zap.String("details", cniErr.Details))
}
}()
// Set supported CNI versions.
pluginInfo := cniVers.PluginSupports(supportedVersions...)
// Parse args and call the appropriate cmd handler.
cniErr := cniSkel.PluginMainWithError(api.Add, api.Get, api.Delete, pluginInfo, plugin.version)
if cniErr != nil {
cniErr.Print()
return cniErr
}
return nil
}
// DelegateAdd calls the given plugin's ADD command and returns the result.
func (plugin *Plugin) DelegateAdd(pluginName string, nwCfg *NetworkConfig) (*cniTypesCurr.Result, error) {
var result *cniTypesCurr.Result
var err error
logger.Info("Calling ADD", zap.String("plugin", pluginName))
defer func() {
logger.Info("Plugin returned",
zap.String("plugin", pluginName),
zap.Any("result", result),
zap.Error(err))
}()
os.Setenv(Cmd, CmdAdd)
res, err := cniInvoke.DelegateAdd(context.TODO(), pluginName, nwCfg.Serialize(), nil)
if err != nil {
return nil, fmt.Errorf("Failed to delegate: %v", err)
}
result, err = cniTypesCurr.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("Failed to convert result: %v", err)
}
return result, nil
}
// DelegateDel calls the given plugin's DEL command and returns the result.
func (plugin *Plugin) DelegateDel(pluginName string, nwCfg *NetworkConfig) error {
var err error
logger.Info("Calling DEL",
zap.String("plugin", pluginName),
zap.Any("config", nwCfg))
defer func() {
logger.Info("Plugin returned",
zap.String("plugin", pluginName),
zap.Error(err))
}()
os.Setenv(Cmd, CmdDel)
err = cniInvoke.DelegateDel(context.TODO(), pluginName, nwCfg.Serialize(), nil)
if err != nil {
return fmt.Errorf("Failed to delegate: %v", err)
}
return nil
}
// Error creates and logs a structured CNI error.
func (plugin *Plugin) Error(err error) *cniTypes.Error {
var cniErr *cniTypes.Error
var ok bool
// Wrap error if necessary.
if cniErr, ok = err.(*cniTypes.Error); !ok {
cniErr = &cniTypes.Error{Code: 100, Msg: err.Error()}
}
logger.Error("error",
zap.String("plugin", plugin.Name),
zap.Error(cniErr))
return cniErr
}
// Errorf creates and logs a custom CNI error according to a format specifier.
func (plugin *Plugin) Errorf(format string, args ...interface{}) *cniTypes.Error {
return plugin.Error(fmt.Errorf(format, args...))
}
// RetriableError logs and returns a CNI error with the TryAgainLater error code
func (plugin *Plugin) RetriableError(err error) *cniTypes.Error {
tryAgainErr := cniTypes.NewError(cniTypes.ErrTryAgainLater, err.Error(), "")
logger.Error("retry failed",
zap.String("name", plugin.Name),
zap.String("error", tryAgainErr.Error()))
return tryAgainErr
}
// Initialize key-value store
func (plugin *Plugin) InitializeKeyValueStore(config *common.PluginConfig) error {
// Create the key value store.
if plugin.Store == nil {
lockclient, err := processlock.NewFileLock(platform.CNILockPath + plugin.Name + store.LockExtension)
if err != nil {
logger.Error("Error initializing file lock", zap.Error(err))
return errors.Wrap(err, "error creating new filelock")
}
plugin.Store, err = store.NewJsonFileStore(platform.CNIRuntimePath+plugin.Name+".json", lockclient, storeLogger)
if err != nil {
logger.Error("Failed to create store", zap.Error(err))
return err
}
}
// Acquire store lock. For windows 1m timeout is used while for Linux 10s timeout is assigned.
var lockTimeoutValue time.Duration = store.DefaultLockTimeoutLinux
if runtime.GOOS == "windows" {
lockTimeoutValue = store.DefaultLockTimeoutWindows
}
// Acquire store lock.
if err := plugin.Store.Lock(lockTimeoutValue); err != nil {
logger.Error("[cni] Failed to lock store", zap.Error(err))
return errors.Wrap(err, "error Acquiring store lock")
}
config.Store = plugin.Store
return nil
}
// Uninitialize key-value store
func (plugin *Plugin) UninitializeKeyValueStore() error {
if plugin.Store != nil {
err := plugin.Store.Unlock()
if err != nil {
logger.Error("Failed to unlock store", zap.Error(err))
return err
}
}
plugin.Store = nil
return nil
}