github.com/mccv1r0/cni@v0.7.0-alpha1/libcni/api.go (about)

     1  // Copyright 2015 CNI authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package libcni
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/containernetworking/cni/pkg/invoke"
    26  	"github.com/containernetworking/cni/pkg/types"
    27  	"github.com/containernetworking/cni/pkg/version"
    28  )
    29  
    30  var (
    31  	CacheDir = "/var/lib/cni"
    32  )
    33  
    34  // A RuntimeConf holds the arguments to one invocation of a CNI plugin
    35  // excepting the network configuration, with the nested exception that
    36  // the `runtimeConfig` from the network configuration is included
    37  // here.
    38  type RuntimeConf struct {
    39  	ContainerID string
    40  	NetNS       string
    41  	IfName      string
    42  	Args        [][2]string
    43  	// A dictionary of capability-specific data passed by the runtime
    44  	// to plugins as top-level keys in the 'runtimeConfig' dictionary
    45  	// of the plugin's stdin data.  libcni will ensure that only keys
    46  	// in this map which match the capabilities of the plugin are passed
    47  	// to the plugin
    48  	CapabilityArgs map[string]interface{}
    49  
    50  	// A cache directory in which to library data.  Defaults to CacheDir
    51  	CacheDir string
    52  }
    53  
    54  type NetworkConfig struct {
    55  	Network *types.NetConf
    56  	Bytes   []byte
    57  }
    58  
    59  type NetworkConfigList struct {
    60  	Name       string
    61  	CNIVersion string
    62  	Plugins    []*NetworkConfig
    63  	Bytes      []byte
    64  }
    65  
    66  type CNI interface {
    67  	AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
    68  	GetNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
    69  	DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error
    70  
    71  	AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
    72  	GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
    73  	DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
    74  }
    75  
    76  type CNIConfig struct {
    77  	Path []string
    78  	exec invoke.Exec
    79  }
    80  
    81  // CNIConfig implements the CNI interface
    82  var _ CNI = &CNIConfig{}
    83  
    84  // NewCNIConfig returns a new CNIConfig object that will search for plugins
    85  // in the given paths and use the given exec interface to run those plugins,
    86  // or if the exec interface is not given, will use a default exec handler.
    87  func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
    88  	return &CNIConfig{
    89  		Path: path,
    90  		exec: exec,
    91  	}
    92  }
    93  
    94  func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
    95  	var err error
    96  
    97  	inject := map[string]interface{}{
    98  		"name":       name,
    99  		"cniVersion": cniVersion,
   100  	}
   101  	// Add previous plugin result
   102  	if prevResult != nil {
   103  		inject["prevResult"] = prevResult
   104  	}
   105  
   106  	// Ensure every config uses the same name and version
   107  	orig, err = InjectConf(orig, inject)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	return injectRuntimeConfig(orig, rt)
   113  }
   114  
   115  // This function takes a libcni RuntimeConf structure and injects values into
   116  // a "runtimeConfig" dictionary in the CNI network configuration JSON that
   117  // will be passed to the plugin on stdin.
   118  //
   119  // Only "capabilities arguments" passed by the runtime are currently injected.
   120  // These capabilities arguments are filtered through the plugin's advertised
   121  // capabilities from its config JSON, and any keys in the CapabilityArgs
   122  // matching plugin capabilities are added to the "runtimeConfig" dictionary
   123  // sent to the plugin via JSON on stdin.  For exmaple, if the plugin's
   124  // capabilities include "portMappings", and the CapabilityArgs map includes a
   125  // "portMappings" key, that key and its value are added to the "runtimeConfig"
   126  // dictionary to be passed to the plugin's stdin.
   127  func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
   128  	var err error
   129  
   130  	rc := make(map[string]interface{})
   131  	for capability, supported := range orig.Network.Capabilities {
   132  		if !supported {
   133  			continue
   134  		}
   135  		if data, ok := rt.CapabilityArgs[capability]; ok {
   136  			rc[capability] = data
   137  		}
   138  	}
   139  
   140  	if len(rc) > 0 {
   141  		orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  
   147  	return orig, nil
   148  }
   149  
   150  // ensure we have a usable exec if the CNIConfig was not given one
   151  func (c *CNIConfig) ensureExec() invoke.Exec {
   152  	if c.exec == nil {
   153  		c.exec = &invoke.DefaultExec{
   154  			RawExec:       &invoke.RawExec{Stderr: os.Stderr},
   155  			PluginDecoder: version.PluginDecoder{},
   156  		}
   157  	}
   158  	return c.exec
   159  }
   160  
   161  func (c *CNIConfig) addOrGetNetwork(command, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
   162  	c.ensureExec()
   163  	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	return invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args(command, rt), c.exec)
   174  }
   175  
   176  // Note that only GET requests should pass an initial prevResult
   177  func (c *CNIConfig) addOrGetNetworkList(command string, prevResult types.Result, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
   178  	var err error
   179  	for _, net := range list.Plugins {
   180  		prevResult, err = c.addOrGetNetwork(command, list.Name, list.CNIVersion, net, prevResult, rt)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  	}
   185  
   186  	return prevResult, nil
   187  }
   188  
   189  func getResultCacheFilePath(netName string, rt *RuntimeConf) string {
   190  	cacheDir := rt.CacheDir
   191  	if cacheDir == "" {
   192  		cacheDir = CacheDir
   193  	}
   194  	return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s", netName, rt.ContainerID))
   195  }
   196  
   197  func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error {
   198  	data, err := json.Marshal(result)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	fname := getResultCacheFilePath(netName, rt)
   203  	if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
   204  		return err
   205  	}
   206  	return ioutil.WriteFile(fname, data, 0600)
   207  }
   208  
   209  func delCachedResult(netName string, rt *RuntimeConf) error {
   210  	fname := getResultCacheFilePath(netName, rt)
   211  	return os.Remove(fname)
   212  }
   213  
   214  func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
   215  	fname := getResultCacheFilePath(netName, rt)
   216  	data, err := ioutil.ReadFile(fname)
   217  	if err != nil {
   218  		// Ignore read errors; the cached result may not exist on-disk
   219  		return nil, nil
   220  	}
   221  
   222  	// Read the version of the cached result
   223  	decoder := version.ConfigDecoder{}
   224  	resultCniVersion, err := decoder.Decode(data)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	// Ensure we can understand the result
   230  	result, err := version.NewResult(resultCniVersion, data)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	// Convert to the config version to ensure plugins get prevResult
   236  	// in the same version as the config.  The cached result version
   237  	// should match the config version unless the config was changed
   238  	// while the container was running.
   239  	result, err = result.GetAsVersion(cniVersion)
   240  	if err != nil && resultCniVersion != cniVersion {
   241  		return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
   242  	}
   243  	return result, err
   244  }
   245  
   246  // AddNetworkList executes a sequence of plugins with the ADD command
   247  func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
   248  	result, err := c.addOrGetNetworkList("ADD", nil, list, rt)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	if err = setCachedResult(result, list.Name, rt); err != nil {
   254  		return nil, fmt.Errorf("failed to set network '%s' cached result: %v", list.Name, err)
   255  	}
   256  
   257  	return result, nil
   258  }
   259  
   260  // GetNetworkList executes a sequence of plugins with the GET command
   261  func (c *CNIConfig) GetNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
   262  	// GET was added in CNI spec version 0.4.0 and higher
   263  	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
   264  		return nil, err
   265  	} else if !gtet {
   266  		return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion)
   267  	}
   268  
   269  	cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt)
   270  	if err != nil {
   271  		return nil, fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err)
   272  	}
   273  	return c.addOrGetNetworkList("GET", cachedResult, list, rt)
   274  }
   275  
   276  func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
   277  	c.ensureExec()
   278  	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	return invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
   289  }
   290  
   291  // DelNetworkList executes a sequence of plugins with the DEL command
   292  func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error {
   293  	var cachedResult types.Result
   294  
   295  	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
   296  	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
   297  		return err
   298  	} else if gtet {
   299  		cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
   300  		if err != nil {
   301  			return fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err)
   302  		}
   303  	}
   304  
   305  	for i := len(list.Plugins) - 1; i >= 0; i-- {
   306  		net := list.Plugins[i]
   307  		if err := c.delNetwork(list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
   308  			return err
   309  		}
   310  	}
   311  	_ = delCachedResult(list.Name, rt)
   312  
   313  	return nil
   314  }
   315  
   316  // AddNetwork executes the plugin with the ADD command
   317  func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
   318  	result, err := c.addOrGetNetwork("ADD", net.Network.Name, net.Network.CNIVersion, net, nil, rt)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	if err = setCachedResult(result, net.Network.Name, rt); err != nil {
   324  		return nil, fmt.Errorf("failed to set network '%s' cached result: %v", net.Network.Name, err)
   325  	}
   326  
   327  	return result, nil
   328  }
   329  
   330  // GetNetwork executes the plugin with the GET command
   331  func (c *CNIConfig) GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
   332  	// GET was added in CNI spec version 0.4.0 and higher
   333  	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
   334  		return nil, err
   335  	} else if !gtet {
   336  		return nil, fmt.Errorf("configuration version %q does not support the GET command", net.Network.CNIVersion)
   337  	}
   338  
   339  	cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
   340  	if err != nil {
   341  		return nil, fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err)
   342  	}
   343  	return c.addOrGetNetwork("GET", net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
   344  }
   345  
   346  // DelNetwork executes the plugin with the DEL command
   347  func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
   348  	var cachedResult types.Result
   349  
   350  	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
   351  	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
   352  		return err
   353  	} else if gtet {
   354  		cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
   355  		if err != nil {
   356  			return fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err)
   357  		}
   358  	}
   359  
   360  	if err := c.delNetwork(net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
   361  		return err
   362  	}
   363  	_ = delCachedResult(net.Network.Name, rt)
   364  	return nil
   365  }
   366  
   367  // GetVersionInfo reports which versions of the CNI spec are supported by
   368  // the given plugin.
   369  func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
   370  	c.ensureExec()
   371  	pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	return invoke.GetVersionInfo(pluginPath, c.exec)
   377  }
   378  
   379  // =====
   380  func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
   381  	return &invoke.Args{
   382  		Command:     action,
   383  		ContainerID: rt.ContainerID,
   384  		NetNS:       rt.NetNS,
   385  		PluginArgs:  rt.Args,
   386  		IfName:      rt.IfName,
   387  		Path:        strings.Join(c.Path, string(os.PathListSeparator)),
   388  	}
   389  }