From 2b00c64305f2d617bea96c9cde768738a0636561 Mon Sep 17 00:00:00 2001 From: Michael Cambria Date: Wed, 11 Sep 2019 16:29:32 -0400 Subject: [PATCH] Initial libpod vendor update for CNI and ocicni Signed-off-by: Michael Cambria --- libpod/image/image.go | 6 - libpod/networking_linux.go | 49 +- vendor.conf | 4 +- .../containernetworking/cni/README.md | 8 +- .../containernetworking/cni/libcni/api.go | 233 +++++++- .../containernetworking/cni/libcni/conf.go | 4 +- .../cni/pkg/invoke/args.go | 70 ++- .../cni/pkg/invoke/delegate.go | 29 +- .../cni/pkg/invoke/raw_exec.go | 4 +- .../containernetworking/cni/pkg/types/args.go | 2 +- .../cni/pkg/types/types.go | 25 +- .../cni/pkg/utils/utils.go | 51 ++ vendor/github.com/cri-o/ocicni/go.mod | 11 + .../cri-o/ocicni/pkg/ocicni/ocicni.go | 540 +++++++++++++----- .../cri-o/ocicni/pkg/ocicni/types.go | 69 ++- vendor/github.com/cri-o/ocicni/vendor.conf | 13 - 16 files changed, 864 insertions(+), 254 deletions(-) create mode 100644 vendor/github.com/containernetworking/cni/pkg/utils/utils.go create mode 100644 vendor/github.com/cri-o/ocicni/go.mod delete mode 100644 vendor/github.com/cri-o/ocicni/vendor.conf diff --git a/libpod/image/image.go b/libpod/image/image.go index 89a68a1bde..bcdafb23af 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -11,7 +11,6 @@ import ( "syscall" "time" - types2 "github.com/containernetworking/cni/pkg/types" cp "github.com/containers/image/copy" "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" @@ -389,11 +388,6 @@ func (i *Image) Remove(ctx context.Context, force bool) error { return nil } -// Decompose an Image -func (i *Image) Decompose() error { - return types2.NotImplementedError -} - // TODO: Rework this method to not require an assembly of the fq name with transport /* // GetManifest tries to GET an images manifest, returns nil on success and err on failure diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index ed9ad5f0d6..856ee696d6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -27,25 +27,36 @@ import ( ) // Get an OCICNI network config -func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP) ocicni.PodNetwork { - network := ocicni.PodNetwork{ - Name: name, - Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces - ID: id, - NetNS: nsPath, - PortMappings: ports, - Networks: networks, +func (r *Runtime) getPodNetwork(id, name, nsPath string, config *ContainerConfig, staticIP net.IP) ocicni.PodNetwork { + networks := make([]ocicni.NetAttachment, 0) + for _, netName := range config.Networks { + networks = append(networks, ocicni.NetAttachment{Name: netName}) + } + if len(networks) == 0 { + networks = append(networks, ocicni.NetAttachment{Name: r.netPlugin.GetDefaultNetworkName()}) } + firstNetworkName := networks[0].Name + podNetwork := ocicni.PodNetwork{ + Name: name, + Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces + ID: id, + NetNS: nsPath, + Networks: networks, + RuntimeConfig: map[string]ocicni.RuntimeConfig{ + firstNetworkName: {}, + }, + } + firstNetwork := podNetwork.RuntimeConfig[firstNetworkName] + if len(config.PortMappings) != 0 { + firstNetwork.PortMappings = config.PortMappings + } if staticIP != nil { - defaultNetwork := r.netPlugin.GetDefaultNetworkName() - - network.Networks = []string{defaultNetwork} - network.NetworkConfig = make(map[string]ocicni.NetworkConfig) - network.NetworkConfig[defaultNetwork] = ocicni.NetworkConfig{IP: staticIP.String()} + firstNetwork.IP = staticIP.String() } + podNetwork.RuntimeConfig[firstNetworkName] = firstNetwork - return network + return podNetwork } // Create and configure a new network namespace for a container @@ -59,7 +70,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re requestedIP = ctr.config.StaticIP } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.Config(), requestedIP) results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -75,10 +86,10 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re networkStatus := make([]*cnitypes.Result, 0) for idx, r := range results { - logrus.Debugf("[%d] CNI result: %v", idx, r.String()) - resultCurrent, err := cnitypes.GetResult(r) + logrus.Debugf("[%d] CNI result: %v", idx, r.Result) + resultCurrent, err := cnitypes.GetResult(r.Result) if err != nil { - return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err) + return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.Result, err) } networkStatus = append(networkStatus, resultCurrent) } @@ -408,7 +419,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { requestedIP = ctr.config.StaticIP } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.Config(), requestedIP) // The network may have already been torn down, so don't fail here, just log if err := r.netPlugin.TearDownPod(podNetwork); err != nil { diff --git a/vendor.conf b/vendor.conf index 9b9044b66b..a6f710411d 100644 --- a/vendor.conf +++ b/vendor.conf @@ -13,7 +13,7 @@ github.com/buger/goterm c206103e1f37c0c6c5c039706305ea2aa6e8ad3b github.com/checkpoint-restore/go-criu v3.11 github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1 github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d -github.com/containernetworking/cni v0.7.0-rc2 +github.com/containernetworking/cni 83439463f7840005184ec4c488c9edd6ed6978fc github.com/containernetworking/plugins v0.7.4 github.com/containers/image v2.0.0 github.com/vbauerster/mpb v3.3.4 @@ -23,7 +23,7 @@ github.com/containers/storage v1.12.10 github.com/containers/psgo v1.3.0 github.com/coreos/go-systemd v17 github.com/coreos/pkg v4 -github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb +github.com/cri-o/ocicni 7bd73e9a7f59107c76605aecd2c8ec3d69ef3ff8 github.com/cyphar/filepath-securejoin v0.2.1 github.com/davecgh/go-spew v1.1.0 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 diff --git a/vendor/github.com/containernetworking/cni/README.md b/vendor/github.com/containernetworking/cni/README.md index 3968d908a2..8b0fc9e136 100644 --- a/vendor/github.com/containernetworking/cni/README.md +++ b/vendor/github.com/containernetworking/cni/README.md @@ -9,7 +9,7 @@ # Community Sync Meeting -There is a community sync meeting for users and developers every 1-2 months. The next meeting will help on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc). +There is a community sync meeting for users and developers every 1-2 months. The next meeting will be held on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc). The next meeting will be held on *Wednesday, January 30th, 2019* at *4:00pm UTC / 11:00am EDT / 8:00am PDT* [Add to Calendar](https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2019-01-30&sln=16-17). @@ -67,6 +67,8 @@ To avoid duplication, we think it is prudent to define a common interface betwee - [Knitter - a CNI plugin supporting multiple networking for Kubernetes](https://github.com/ZTE/Knitter) - [DANM - a CNI-compliant networking solution for TelCo workloads running on Kubernetes](https://github.com/nokia/danm) - [VMware NSX – a CNI plugin that enables automated NSX L2/L3 networking and L4/L7 Load Balancing; network isolation at the pod, node, and cluster level; and zero-trust security policy for your Kubernetes cluster.](https://docs.vmware.com/en/VMware-NSX-T/2.2/com.vmware.nsxt.ncp_kubernetes.doc/GUID-6AFA724E-BB62-4693-B95C-321E8DDEA7E1.html) +- [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override) +- [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway) The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins). @@ -189,13 +191,13 @@ lo Link encap:Local Loopback ## What might CNI do in the future? -CNI currently covers a wide range of needs for network configuration due to it simple model and API. +CNI currently covers a wide range of needs for network configuration due to its simple model and API. However, in the future CNI might want to branch out into other directions: - Dynamic updates to existing network configuration - Dynamic policies for network bandwidth and firewall rules -If these topics of are interest, please contact the team via the mailing list or IRC and find some like-minded people in the community to put a proposal together. +If these topics are of interest, please contact the team via the mailing list or IRC and find some like-minded people in the community to put a proposal together. ## Contact diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go index 360733e740..22b1117422 100644 --- a/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -25,6 +25,7 @@ import ( "github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/utils" "github.com/containernetworking/cni/pkg/version" ) @@ -32,6 +33,10 @@ var ( CacheDir = "/var/lib/cni" ) +const ( + CNICacheV1 = "cniCacheV1" +) + // A RuntimeConf holds the arguments to one invocation of a CNI plugin // excepting the network configuration, with the nested exception that // the `runtimeConfig` from the network configuration is included @@ -48,7 +53,7 @@ type RuntimeConf struct { // to the plugin CapabilityArgs map[string]interface{} - // A cache directory in which to library data. Defaults to CacheDir + // DEPRECATED. Will be removed in a future release. CacheDir string } @@ -69,19 +74,23 @@ type CNI interface { AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error + GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) + GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) + GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) } type CNIConfig struct { - Path []string - exec invoke.Exec + Path []string + exec invoke.Exec + cacheDir string } // CNIConfig implements the CNI interface @@ -91,9 +100,18 @@ var _ CNI = &CNIConfig{} // in the given paths and use the given exec interface to run those plugins, // or if the exec interface is not given, will use a default exec handler. func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig { + return NewCNIConfigWithCacheDir(path, "", exec) +} + +// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins +// in the given paths use the given exec interface to run those plugins, +// or if the exec interface is not given, will use a default exec handler. +// The given cache directory will be used for temporary data storage when needed. +func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig { return &CNIConfig{ - Path: path, - exec: exec, + Path: path, + cacheDir: cacheDir, + exec: exec, } } @@ -164,33 +182,122 @@ func (c *CNIConfig) ensureExec() invoke.Exec { return c.exec } -func getResultCacheFilePath(netName string, rt *RuntimeConf) string { - cacheDir := rt.CacheDir - if cacheDir == "" { - cacheDir = CacheDir - } - return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)) +type cachedInfo struct { + Kind string `json:"kind"` + ContainerID string `json:"containerId"` + Config []byte `json:"config"` + IfName string `json:"ifName"` + NetworkName string `json:"networkName"` + CniArgs [][2]string `json:"cniArgs,omitempty"` + CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"` + RawResult map[string]interface{} `json:"result,omitempty"` + Result types.Result `json:"-"` } -func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error { +// getCacheDir returns the cache directory in this order: +// 1) global cacheDir from CNIConfig object +// 2) deprecated cacheDir from RuntimeConf object +// 3) fall back to default cache directory +func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string { + if c.cacheDir != "" { + return c.cacheDir + } + if rt.CacheDir != "" { + return rt.CacheDir + } + return CacheDir +} + +func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) { + if netName == "" || rt.ContainerID == "" || rt.IfName == "" { + return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName) + } + return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil +} + +func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error { + cached := cachedInfo{ + Kind: CNICacheV1, + ContainerID: rt.ContainerID, + Config: config, + IfName: rt.IfName, + NetworkName: netName, + CniArgs: rt.Args, + CapabilityArgs: rt.CapabilityArgs, + } + + // We need to get type.Result into cachedInfo as JSON map + // Marshal to []byte, then Unmarshal into cached.RawResult data, err := json.Marshal(result) if err != nil { return err } - fname := getResultCacheFilePath(netName, rt) + + err = json.Unmarshal(data, &cached.RawResult) + if err != nil { + return err + } + + newBytes, err := json.Marshal(&cached) + if err != nil { + return err + } + + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return err + } if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil { return err } - return ioutil.WriteFile(fname, data, 0600) + + return ioutil.WriteFile(fname, newBytes, 0600) } -func delCachedResult(netName string, rt *RuntimeConf) error { - fname := getResultCacheFilePath(netName, rt) +func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + // Ignore error + return nil + } return os.Remove(fname) } -func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { - fname := getResultCacheFilePath(netName, rt) +func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + var bytes []byte + + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, nil, err + } + bytes, err = ioutil.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil, nil + } + + unmarshaled := cachedInfo{} + if err := json.Unmarshal(bytes, &unmarshaled); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err) + } + if unmarshaled.Kind != CNICacheV1 { + return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind) + } + + newRt := *rt + if unmarshaled.CniArgs != nil { + newRt.Args = unmarshaled.CniArgs + } + newRt.CapabilityArgs = unmarshaled.CapabilityArgs + + return unmarshaled.Config, &newRt, nil +} + +func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, err + } data, err := ioutil.ReadFile(fname) if err != nil { // Ignore read errors; the cached result may not exist on-disk @@ -221,16 +328,73 @@ func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, return result, err } +func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, err + } + fdata, err := ioutil.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil + } + + cachedInfo := cachedInfo{} + if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 { + return c.getLegacyCachedResult(netName, cniVersion, rt) + } + + newBytes, err := json.Marshal(&cachedInfo.RawResult) + if err != nil { + return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err) + } + + // Read the version of the cached result + decoder := version.ConfigDecoder{} + resultCniVersion, err := decoder.Decode(newBytes) + if err != nil { + return nil, err + } + + // Ensure we can understand the result + result, err := version.NewResult(resultCniVersion, newBytes) + if err != nil { + return nil, err + } + + // Convert to the config version to ensure plugins get prevResult + // in the same version as the config. The cached result version + // should match the config version unless the config was changed + // while the container was running. + result, err = result.GetAsVersion(cniVersion) + if err != nil && resultCniVersion != cniVersion { + return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err) + } + return result, err +} + // GetNetworkListCachedResult returns the cached Result of the previous -// previous AddNetworkList() operation for a network list, or an error. +// AddNetworkList() operation for a network list, or an error. func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { - return getCachedResult(list.Name, list.CNIVersion, rt) + return c.getCachedResult(list.Name, list.CNIVersion, rt) } // GetNetworkCachedResult returns the cached Result of the previous -// previous AddNetwork() operation for a network, or an error. +// AddNetwork() operation for a network, or an error. func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { - return getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) +} + +// GetNetworkListCachedConfig copies the input RuntimeConf to output +// RuntimeConf with fields updated with info from the cached Config. +func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + return c.getCachedConfig(list.Name, rt) +} + +// GetNetworkCachedConfig copies the input RuntimeConf to output +// RuntimeConf with fields updated with info from the cached Config. +func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + return c.getCachedConfig(net.Network.Name, rt) } func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { @@ -239,6 +403,12 @@ func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net if err != nil { return nil, err } + if err := utils.ValidateContainerID(rt.ContainerID); err != nil { + return nil, err + } + if err := utils.ValidateNetworkName(name); err != nil { + return nil, err + } newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) if err != nil { @@ -259,7 +429,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, } } - if err = setCachedResult(result, list.Name, rt); err != nil { + if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil { return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err) } @@ -294,7 +464,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis return nil } - cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt) + cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt) if err != nil { return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err) } @@ -331,7 +501,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { return err } else if gtet { - cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt) + cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt) if err != nil { return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err) } @@ -343,7 +513,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, return err } } - _ = delCachedResult(list.Name, rt) + _ = c.cacheDel(list.Name, rt) return nil } @@ -355,7 +525,7 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt return nil, err } - if err = setCachedResult(result, net.Network.Name, rt); err != nil { + if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil { return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err) } @@ -371,7 +541,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion) } - cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) if err != nil { return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err) } @@ -386,7 +556,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { return err } else if gtet { - cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) if err != nil { return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err) } @@ -395,7 +565,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { return err } - _ = delCachedResult(net.Network.Name, rt) + _ = c.cacheDel(net.Network.Name, rt) return nil } @@ -454,7 +624,8 @@ func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([] // validatePlugin checks that an individual plugin's configuration is sane func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error { - pluginPath, err := invoke.FindInPath(pluginName, c.Path) + c.ensureExec() + pluginPath, err := c.exec.FindInPath(pluginName, c.Path) if err != nil { return err } diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go index ea56c509d0..d8920cf8cd 100644 --- a/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -114,11 +114,11 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { for i, conf := range plugins { newBytes, err := json.Marshal(conf) if err != nil { - return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err) + return nil, fmt.Errorf("failed to marshal plugin config %d: %v", i, err) } netConf, err := ConfFromBytes(newBytes) if err != nil { - return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err) + return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err) } list.Plugins = append(list.Plugins, netConf) } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go index 39b6397230..d31a44e875 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go @@ -15,6 +15,7 @@ package invoke import ( + "fmt" "os" "strings" ) @@ -22,6 +23,8 @@ import ( type CNIArgs interface { // For use with os/exec; i.e., return nil to inherit the // environment from this process + // For use in delegation; inherit the environment from this + // process and allow overrides AsEnv() []string } @@ -29,7 +32,7 @@ type inherited struct{} var inheritArgsFromEnv inherited -func (_ *inherited) AsEnv() []string { +func (*inherited) AsEnv() []string { return nil } @@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string { pluginArgsStr = stringify(args.PluginArgs) } - // Ensure that the custom values are first, so any value present in - // the process environment won't override them. - env = append([]string{ - "CNI_COMMAND=" + args.Command, - "CNI_CONTAINERID=" + args.ContainerID, - "CNI_NETNS=" + args.NetNS, - "CNI_ARGS=" + pluginArgsStr, - "CNI_IFNAME=" + args.IfName, - "CNI_PATH=" + args.Path, - }, env...) - return env + // Duplicated values which come first will be overrided, so we must put the + // custom values in the end to avoid being overrided by the process environments. + env = append(env, + "CNI_COMMAND="+args.Command, + "CNI_CONTAINERID="+args.ContainerID, + "CNI_NETNS="+args.NetNS, + "CNI_ARGS="+pluginArgsStr, + "CNI_IFNAME="+args.IfName, + "CNI_PATH="+args.Path, + ) + return dedupEnv(env) } // taken from rkt/networking/net_plugin.go @@ -80,3 +83,46 @@ func stringify(pluginArgs [][2]string) string { return strings.Join(entries, ";") } + +// DelegateArgs implements the CNIArgs interface +// used for delegation to inherit from environments +// and allow some overrides like CNI_COMMAND +var _ CNIArgs = &DelegateArgs{} + +type DelegateArgs struct { + Command string +} + +func (d *DelegateArgs) AsEnv() []string { + env := os.Environ() + + // The custom values should come in the end to override the existing + // process environment of the same key. + env = append(env, + "CNI_COMMAND="+d.Command, + ) + return dedupEnv(env) +} + +// dedupEnv returns a copy of env with any duplicates removed, in favor of later values. +// Items not of the normal environment "key=value" form are preserved unchanged. +func dedupEnv(env []string) []string { + out := make([]string, 0, len(env)) + envMap := map[string]string{} + + for _, kv := range env { + // find the first "=" in environment, if not, just keep it + eq := strings.Index(kv, "=") + if eq < 0 { + out = append(out, kv) + continue + } + envMap[kv[:eq]] = kv[eq+1:] + } + + for k, v := range envMap { + out = append(out, fmt.Sprintf("%s=%s", k, v)) + } + + return out +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go index 30b4672f11..8defe4dd39 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -16,22 +16,17 @@ package invoke import ( "context" - "fmt" "os" "path/filepath" "github.com/containernetworking/cni/pkg/types" ) -func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string, Exec, error) { +func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) { if exec == nil { exec = defaultExec } - if os.Getenv("CNI_COMMAND") != expectedCommand { - return "", nil, fmt.Errorf("CNI_COMMAND is not " + expectedCommand) - } - paths := filepath.SplitList(os.Getenv("CNI_PATH")) pluginPath, err := exec.FindInPath(delegatePlugin, paths) if err != nil { @@ -44,32 +39,42 @@ func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string, // DelegateAdd calls the given delegate plugin with the CNI ADD action and // JSON configuration func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { - pluginPath, realExec, err := delegateCommon("ADD", delegatePlugin, exec) + pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) if err != nil { return nil, err } - return ExecPluginWithResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) + // DelegateAdd will override the original "CNI_COMMAND" env from process with ADD + return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec) } // DelegateCheck calls the given delegate plugin with the CNI CHECK action and // JSON configuration func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { - pluginPath, realExec, err := delegateCommon("CHECK", delegatePlugin, exec) + pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) if err != nil { return err } - return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) + // DelegateCheck will override the original CNI_COMMAND env from process with CHECK + return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec) } // DelegateDel calls the given delegate plugin with the CNI DEL action and // JSON configuration func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { - pluginPath, realExec, err := delegateCommon("DEL", delegatePlugin, exec) + pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) if err != nil { return err } - return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) + // DelegateDel will override the original CNI_COMMAND env from process with DEL + return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec) +} + +// return CNIArgs used by delegation +func delegateArgs(action string) *DelegateArgs { + return &DelegateArgs{ + Command: action, + } } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go index e5b86634d6..ad8498ba27 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -46,7 +46,9 @@ func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData [ func pluginErr(err error, output []byte) error { if _, ok := err.(*exec.ExitError); ok { emsg := types.Error{} - if perr := json.Unmarshal(output, &emsg); perr != nil { + if len(output) == 0 { + emsg.Msg = "netplugin failed with no error message" + } else if perr := json.Unmarshal(output, &emsg); perr != nil { emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr) } return &emsg diff --git a/vendor/github.com/containernetworking/cni/pkg/types/args.go b/vendor/github.com/containernetworking/cni/pkg/types/args.go index bd8640fc96..4eac648994 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/args.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/args.go @@ -36,7 +36,7 @@ func (b *UnmarshallableBool) UnmarshalText(data []byte) error { case "0", "false": *b = false default: - return fmt.Errorf("Boolean unmarshal error: invalid input %s", s) + return fmt.Errorf("boolean unmarshal error: invalid input %s", s) } return nil } diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go index d0d11006a0..3e185c1cea 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -16,7 +16,6 @@ package types import ( "encoding/json" - "errors" "fmt" "io" "net" @@ -134,9 +133,16 @@ func (r *Route) String() string { // Well known error codes // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes const ( - ErrUnknown uint = iota // 0 - ErrIncompatibleCNIVersion // 1 - ErrUnsupportedField // 2 + ErrUnknown uint = iota // 0 + ErrIncompatibleCNIVersion // 1 + ErrUnsupportedField // 2 + ErrUnknownContainer // 3 + ErrInvalidEnvironmentVariables // 4 + ErrIOFailure // 5 + ErrDecodingFailure // 6 + ErrInvalidNetworkConfig // 7 + ErrTryAgainLater uint = 11 + ErrInternal uint = 999 ) type Error struct { @@ -145,6 +151,14 @@ type Error struct { Details string `json:"details,omitempty"` } +func NewError(code uint, msg, details string) *Error { + return &Error{ + Code: code, + Msg: msg, + Details: details, + } +} + func (e *Error) Error() string { details := "" if e.Details != "" { @@ -194,6 +208,3 @@ func prettyPrint(obj interface{}) error { _, err = os.Stdout.Write(data) return err } - -// NotImplementedError is used to indicate that a method is not implemented for the given platform -var NotImplementedError = errors.New("Not Implemented") diff --git a/vendor/github.com/containernetworking/cni/pkg/utils/utils.go b/vendor/github.com/containernetworking/cni/pkg/utils/utils.go new file mode 100644 index 0000000000..324c40dea4 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/utils/utils.go @@ -0,0 +1,51 @@ +// Copyright 2019 CNI 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 utils + +import ( + "regexp" + + "github.com/containernetworking/cni/pkg/types" +) + +// cniValidNameChars is the regexp used to validate valid characters in +// containerID and networkName +const cniValidNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.\-]` + +var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`) + +// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters +func ValidateContainerID(containerID string) *types.Error { + + if containerID == "" { + return types.NewError(types.ErrUnknownContainer, "missing containerID", "") + } + if !cniReg.MatchString(containerID) { + return types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", containerID) + } + return nil +} + +// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters +func ValidateNetworkName(networkName string) *types.Error { + + if networkName == "" { + return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "") + } + if !cniReg.MatchString(networkName) { + return types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", networkName) + } + return nil +} diff --git a/vendor/github.com/cri-o/ocicni/go.mod b/vendor/github.com/cri-o/ocicni/go.mod new file mode 100644 index 0000000000..734dc6e059 --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/go.mod @@ -0,0 +1,11 @@ +module github.com/cri-o/ocicni + +go 1.12 + +require ( + github.com/containernetworking/cni v0.7.2-0.20190725021623-1318d7c94c41 + github.com/fsnotify/fsnotify v1.4.7 + github.com/onsi/ginkgo v1.8.0 + github.com/onsi/gomega v1.5.0 + github.com/sirupsen/logrus v1.4.2 +) diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go index a08be9ecdb..88d18069e0 100644 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go @@ -2,11 +2,14 @@ package ocicni import ( "context" + "encoding/json" "errors" "fmt" + "io/ioutil" "net" "os" "path" + "path/filepath" "sort" "strings" "sync" @@ -21,10 +24,11 @@ import ( ) type cniNetworkPlugin struct { + cniConfig *libcni.CNIConfig loNetwork *cniNetwork sync.RWMutex - defaultNetName string + defaultNetName netName networks map[string]*cniNetwork nsManager *nsManager @@ -47,11 +51,15 @@ type cniNetworkPlugin struct { cacheDir string } +type netName struct { + name string + changeable bool +} + type cniNetwork struct { - name string - filePath string - NetworkConfig *libcni.NetworkConfigList - CNIConfig *libcni.CNIConfig + name string + filePath string + config *libcni.NetworkConfigList } var errMissingDefaultNetwork = errors.New("Missing CNI default network") @@ -186,6 +194,8 @@ func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) { // If defaultNetName is not empty, a CNI config with that network name will // be used as the default CNI network, and container network operations will // fail until that network config is present and valid. +// If defaultNetName is empty, CNI config files should be reloaded real-time and +// defaultNetName should be changeable and determined by file sorting. func InitCNI(defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) { return initCNI(nil, "", defaultNetName, confDir, binDirs...) } @@ -198,17 +208,24 @@ func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir strin if len(binDirs) == 0 { binDirs = []string{DefaultBinDir} } + plugin := &cniNetworkPlugin{ - defaultNetName: defaultNetName, - networks: make(map[string]*cniNetwork), - loNetwork: getLoNetwork(exec, binDirs), - confDir: confDir, - binDirs: binDirs, - shutdownChan: make(chan struct{}), - done: &sync.WaitGroup{}, - pods: make(map[string]*podLock), - exec: exec, - cacheDir: cacheDir, + cniConfig: libcni.NewCNIConfig(binDirs, exec), + defaultNetName: netName{ + name: defaultNetName, + // If defaultNetName is not assigned in initialization, + // it should be changeable + changeable: defaultNetName == "", + }, + networks: make(map[string]*cniNetwork), + loNetwork: getLoNetwork(), + confDir: confDir, + binDirs: binDirs, + shutdownChan: make(chan struct{}), + done: &sync.WaitGroup{}, + pods: make(map[string]*podLock), + exec: exec, + cacheDir: cacheDir, } if exec == nil { @@ -246,7 +263,7 @@ func (plugin *cniNetworkPlugin) Shutdown() error { return nil } -func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[string]*cniNetwork, string, error) { +func loadNetworks(confDir string, cni *libcni.CNIConfig) (map[string]*cniNetwork, string, error) { files, err := libcni.ConfFiles(confDir, []string{".conf", ".conflist", ".json"}) if err != nil { return nil, "", err @@ -284,17 +301,30 @@ func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[st logrus.Warningf("CNI config list %s has no networks, skipping", confFile) continue } + + // Validation on CNI config should be done to pre-check presence + // of plugins which are necessary. + if _, err := cni.ValidateNetworkList(context.TODO(), confList); err != nil { + logrus.Warningf("Error validating CNI config file %s: %v", confFile, err) + continue + } + if confList.Name == "" { confList.Name = path.Base(confFile) } + cniNet := &cniNetwork{ + name: confList.Name, + filePath: confFile, + config: confList, + } + logrus.Infof("Found CNI network %s (type=%v) at %s", confList.Name, confList.Plugins[0].Network.Type, confFile) - networks[confList.Name] = &cniNetwork{ - name: confList.Name, - filePath: confFile, - NetworkConfig: confList, - CNIConfig: libcni.NewCNIConfig(binDirs, exec), + if _, ok := networks[confList.Name]; !ok { + networks[confList.Name] = cniNet + } else { + logrus.Infof("Ignore CNI network %s (type=%v) at %s because already exists", confList.Name, confList.Plugins[0].Network.Type, confFile) } if defaultNetName == "" { @@ -305,39 +335,49 @@ func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[st return networks, defaultNetName, nil } -func getLoNetwork(exec cniinvoke.Exec, binDirs []string) *cniNetwork { - loConfig, err := libcni.ConfListFromBytes([]byte(`{ - "cniVersion": "0.2.0", - "name": "cni-loopback", +const ( + loIfname string = "lo" + loNetname string = "cni-loopback" +) + +func getLoNetwork() *cniNetwork { + loConfig, err := libcni.ConfListFromBytes([]byte(fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "%s", "plugins": [{ "type": "loopback" }] -}`)) +}`, loNetname))) if err != nil { // The hardcoded config above should always be valid and unit tests will // catch this panic(err) } loNetwork := &cniNetwork{ - name: "lo", - NetworkConfig: loConfig, - CNIConfig: libcni.NewCNIConfig(binDirs, exec), + name: loIfname, + config: loConfig, } return loNetwork } func (plugin *cniNetworkPlugin) syncNetworkConfig() error { - networks, defaultNetName, err := loadNetworks(plugin.exec, plugin.confDir, plugin.binDirs) + networks, defaultNetName, err := loadNetworks(plugin.confDir, plugin.cniConfig) if err != nil { return err } plugin.Lock() defer plugin.Unlock() - if plugin.defaultNetName == "" { - plugin.defaultNetName = defaultNetName + + // Update defaultNetName if it is changeable + if plugin.defaultNetName.changeable { + plugin.defaultNetName.name = defaultNetName + logrus.Infof("Update default CNI network name to %s", defaultNetName) + } else { + logrus.Warnf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name) } + plugin.networks = networks return nil @@ -356,7 +396,7 @@ func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) { func (plugin *cniNetworkPlugin) GetDefaultNetworkName() string { plugin.RLock() defer plugin.RUnlock() - return plugin.defaultNetName + return plugin.defaultNetName.name } func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { @@ -382,27 +422,120 @@ func (plugin *cniNetworkPlugin) Name() string { return CNIPluginName } -func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork) error) error { +func (plugin *cniNetworkPlugin) loadNetworkFromCache(name string, rt *libcni.RuntimeConf) (*cniNetwork, *libcni.RuntimeConf, error) { + cniNet := &cniNetwork{ + name: name, + config: &libcni.NetworkConfigList{ + Name: name, + }, + } + + var confBytes []byte + var err error + confBytes, rt, err = plugin.cniConfig.GetNetworkListCachedConfig(cniNet.config, rt) + if err != nil { + return nil, nil, err + } else if confBytes == nil { + return nil, nil, fmt.Errorf("network %q not found in CNI cache", name) + } + + cniNet.config, err = libcni.ConfListFromBytes(confBytes) + if err != nil { + // Might be a plain NetworkConfig + netConf, err := libcni.ConfFromBytes(confBytes) + if err != nil { + return nil, nil, err + } + // Up-convert to a NetworkConfigList + cniNet.config, err = libcni.ConfListFromConf(netConf) + if err != nil { + return nil, nil, err + } + } + + return cniNet, rt, nil +} + +type forEachNetworkFn func(*cniNetwork, *PodNetwork, *libcni.RuntimeConf) error + +func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, fromCache bool, actionFn forEachNetworkFn) error { networks := podNetwork.Networks if len(networks) == 0 { - networks = append(networks, plugin.GetDefaultNetworkName()) + networks = append(networks, NetAttachment{ + Name: plugin.GetDefaultNetworkName(), + }) } - for i, netName := range networks { - // Interface names start at "eth0" and count up for each network - ifName := fmt.Sprintf("eth%d", i) - network, err := plugin.getNetwork(netName) + + allIfNames := make(map[string]bool) + for _, req := range networks { + if req.Ifname != "" { + // Make sure the requested name isn't already assigned + if allIfNames[req.Ifname] { + return fmt.Errorf("network %q requested interface name %q already assigned", req.Name, req.Ifname) + } + allIfNames[req.Ifname] = true + } + } + + for _, network := range networks { + ifName := network.Ifname + if ifName == "" { + for i := 0; i < 10000; i++ { + candidate := fmt.Sprintf("eth%d", i) + if !allIfNames[candidate] { + allIfNames[candidate] = true + ifName = candidate + break + } + } + if ifName == "" { + return fmt.Errorf("failed to find free interface name for network %q", network.Name) + } + } + + rt, err := buildCNIRuntimeConf(plugin.cacheDir, podNetwork, ifName, podNetwork.RuntimeConfig[network.Name]) if err != nil { - logrus.Errorf(err.Error()) + logrus.Errorf("error building CNI runtime config: %v", err) return err } - if err := forEachFunc(network, ifName, podNetwork); err != nil { + + var cniNet *cniNetwork + if fromCache { + var newRt *libcni.RuntimeConf + cniNet, newRt, err = plugin.loadNetworkFromCache(network.Name, rt) + if err != nil { + logrus.Errorf("error loading cached network config: %v", err) + // fall back to loading from existing plugins on disk + } else { + // Use the updated RuntimeConf + rt = newRt + } + } + if cniNet == nil { + cniNet, err = plugin.getNetwork(network.Name) + if err != nil { + logrus.Errorf(err.Error()) + return err + } + } + + if err := actionFn(cniNet, podNetwork, rt); err != nil { return err } } return nil } -func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Result, error) { +func buildLoopbackRuntimeConf(cacheDir string, podNetwork *PodNetwork) *libcni.RuntimeConf { + return &libcni.RuntimeConf{ + ContainerID: podNetwork.ID, + NetNS: podNetwork.NetNS, + CacheDir: cacheDir, + IfName: loIfname, + } +} + +func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]NetResult, error) { if err := plugin.networksAvailable(&podNetwork); err != nil { return nil, err } @@ -410,89 +543,24 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu plugin.podLock(podNetwork).Lock() defer plugin.podUnlock(podNetwork) - _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo", "") - if err != nil { + loRt := buildLoopbackRuntimeConf(plugin.cacheDir, &podNetwork) + if _, err := plugin.loNetwork.addToNetwork(loRt, plugin.cniConfig); err != nil { logrus.Errorf("Error while adding to cni lo network: %s", err) return nil, err } - results := make([]cnitypes.Result, 0) - if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error { - ip := "" - if conf, ok := podNetwork.NetworkConfig[network.name]; ok { - ip = conf.IP - } - - result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName, ip) + results := make([]NetResult, 0) + if err := plugin.forEachNetwork(&podNetwork, false, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + result, err := network.addToNetwork(rt, plugin.cniConfig) if err != nil { logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err) return err } - results = append(results, result) - return nil - }); err != nil { - return nil, err - } - - return results, nil -} - -func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error { - if err := plugin.networksAvailable(&podNetwork); err != nil { - return err - } - - plugin.podLock(podNetwork).Lock() - defer plugin.podUnlock(podNetwork) - - return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error { - ip := "" - if conf, ok := podNetwork.NetworkConfig[network.name]; ok { - ip = conf.IP - } - - if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName, ip); err != nil { - logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err) - return err - } - return nil - }) -} - -// GetPodNetworkStatus returns IP addressing and interface details for all -// networks attached to the pod. -func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cnitypes.Result, error) { - plugin.podLock(podNetwork).Lock() - defer plugin.podUnlock(podNetwork) - - results := make([]cnitypes.Result, 0) - if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error { - version := "4" - ip, mac, err := getContainerDetails(plugin.nsManager, podNetwork.NetNS, ifName, "-4") - if err != nil { - ip, mac, err = getContainerDetails(plugin.nsManager, podNetwork.NetNS, ifName, "-6") - if err != nil { - return err - } - version = "6" - } - - // Until CNI's GET request lands, construct the Result manually - results = append(results, &cnicurrent.Result{ - CNIVersion: "0.3.1", - Interfaces: []*cnicurrent.Interface{ - { - Name: ifName, - Mac: mac.String(), - Sandbox: podNetwork.NetNS, - }, - }, - IPs: []*cnicurrent.IPConfig{ - { - Version: version, - Interface: cnicurrent.Int(0), - Address: *ip, - }, + results = append(results, NetResult{ + Result: result, + NetAttachment: NetAttachment{ + Name: network.name, + Ifname: rt.IfName, }, }) return nil @@ -503,16 +571,139 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cn return results, nil } -func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (cnitypes.Result, error) { - rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip) +func (plugin *cniNetworkPlugin) getCachedNetworkInfo(containerID string) ([]NetAttachment, error) { + cacheDir := libcni.CacheDir + if plugin.cacheDir != "" { + cacheDir = plugin.cacheDir + } + + dirPath := filepath.Join(cacheDir, "results") + entries, err := ioutil.ReadDir(dirPath) if err != nil { - logrus.Errorf("Error adding network: %v", err) return nil, err } - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to add CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) - res, err := cninet.AddNetworkList(context.Background(), netconf, rt) + fileNames := make([]string, 0, len(entries)) + for _, e := range entries { + fileNames = append(fileNames, e.Name()) + } + sort.Strings(fileNames) + + attachments := []NetAttachment{} + for _, fname := range fileNames { + part := fmt.Sprintf("-%s-", containerID) + pos := strings.Index(fname, part) + if pos <= 0 || pos+len(part) >= len(fname) { + continue + } + + cacheFile := filepath.Join(dirPath, fname) + bytes, err := ioutil.ReadFile(cacheFile) + if err != nil { + logrus.Warningf("failed to read CNI cache file %s: %v", cacheFile, err) + continue + } + + cachedInfo := struct { + Kind string `json:"kind"` + IfName string `json:"ifName"` + ContainerID string `json:"containerID"` + NetName string `json:"networkName"` + }{} + + if err := json.Unmarshal(bytes, &cachedInfo); err != nil { + logrus.Warningf("failed to unmarshal CNI cache file %s: %v", cacheFile, err) + continue + } + if cachedInfo.Kind != libcni.CNICacheV1 { + logrus.Warningf("unknown CNI cache file %s kind %q", cacheFile, cachedInfo.Kind) + continue + } + if cachedInfo.ContainerID != containerID { + continue + } + // Ignore the loopback interface; it's handled separately + if cachedInfo.IfName == loIfname && cachedInfo.NetName == loNetname { + continue + } + if cachedInfo.IfName == "" || cachedInfo.NetName == "" { + logrus.Warningf("missing CNI cache file %s ifname %q or netname %q", cacheFile, cachedInfo.IfName, cachedInfo.NetName) + continue + } + + attachments = append(attachments, NetAttachment{ + Name: cachedInfo.NetName, + Ifname: cachedInfo.IfName, + }) + } + return attachments, nil +} + +// TearDownPod tears down pod networks. Prefers cached pod attachment information +// but falls back to given network attachment information. +func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error { + if len(podNetwork.Networks) == 0 { + attachments, err := plugin.getCachedNetworkInfo(podNetwork.ID) + if err == nil && len(attachments) > 0 { + podNetwork.Networks = attachments + } + } + + if err := plugin.networksAvailable(&podNetwork); err != nil { + return err + } + + loRt := buildLoopbackRuntimeConf(plugin.cacheDir, &podNetwork) + if err := plugin.loNetwork.deleteFromNetwork(loRt, plugin.cniConfig); err != nil { + logrus.Errorf("Error while removing pod from CNI lo network: %v", err) + // Loopback teardown errors are not fatal + } + + plugin.podLock(podNetwork).Lock() + defer plugin.podUnlock(podNetwork) + + return plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + if err := network.deleteFromNetwork(rt, plugin.cniConfig); err != nil { + logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err) + return err + } + return nil + }) +} + +// GetPodNetworkStatus returns IP addressing and interface details for all +// networks attached to the pod. +func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]NetResult, error) { + plugin.podLock(podNetwork).Lock() + defer plugin.podUnlock(podNetwork) + + results := make([]NetResult, 0) + if err := plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + result, err := network.checkNetwork(rt, plugin.cniConfig, plugin.nsManager, podNetwork.NetNS) + if err != nil { + logrus.Errorf("Error while checking pod to CNI network %q: %s", network.name, err) + return err + } + if result != nil { + results = append(results, NetResult{ + Result: result, + NetAttachment: NetAttachment{ + Name: network.name, + Ifname: rt.IfName, + }, + }) + } + return nil + }); err != nil { + return nil, err + } + + return results, nil +} + +func (network *cniNetwork) addToNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig) (cnitypes.Result, error) { + logrus.Infof("About to add CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) + res, err := cni.AddNetworkList(context.Background(), network.config, rt) if err != nil { logrus.Errorf("Error adding network: %v", err) return nil, err @@ -521,24 +712,88 @@ func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, return res, nil } -func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) error { - rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip) +func (network *cniNetwork) checkNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig, nsManager *nsManager, netns string) (cnitypes.Result, error) { + logrus.Infof("About to check CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) + + gtet, err := cniversion.GreaterThanOrEqualTo(network.config.CNIVersion, "0.4.0") if err != nil { - logrus.Errorf("Error deleting network: %v", err) - return err + return nil, err } - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to del CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) - err = cninet.DelNetworkList(context.Background(), netconf, rt) + var result cnitypes.Result + + // When CNIVersion supports Check, use it. Otherwise fall back on what was done initially. + if gtet { + err = cni.CheckNetworkList(context.Background(), network.config, rt) + logrus.Infof("Checking CNI network %s (config version=%v)", network.name, network.config.CNIVersion) + if err != nil { + logrus.Errorf("Error checking network: %v", err) + return nil, err + } + } + + result, err = cni.GetNetworkListCachedResult(network.config, rt) if err != nil { + logrus.Errorf("Error GetNetworkListCachedResult: %v", err) + return nil, err + } else if result != nil { + return result, nil + } + + // result doesn't exist, create one + logrus.Infof("Checking CNI network %s (config version=%v) nsManager=%v", network.name, network.config.CNIVersion, nsManager) + + var cniInterface *cnicurrent.Interface + ips := []*cnicurrent.IPConfig{} + errs := []error{} + for _, version := range []string{"4", "6"} { + ip, mac, err := getContainerDetails(nsManager, netns, rt.IfName, "-"+version) + if err == nil { + if cniInterface == nil { + cniInterface = &cnicurrent.Interface{ + Name: rt.IfName, + Mac: mac.String(), + Sandbox: netns, + } + } + ips = append(ips, &cnicurrent.IPConfig{ + Version: version, + Interface: cnicurrent.Int(0), + Address: *ip, + }) + } else { + errs = append(errs, err) + } + } + if cniInterface == nil || len(ips) == 0 { + return nil, fmt.Errorf("neither IPv4 nor IPv6 found when retrieving network status: %v", errs) + } + + result = &cnicurrent.Result{ + CNIVersion: network.config.CNIVersion, + Interfaces: []*cnicurrent.Interface{cniInterface}, + IPs: ips, + } + + // Result must be the same CNIVersion as the CNI config + converted, err := result.GetAsVersion(network.config.CNIVersion) + if err != nil { + return nil, err + } + + return converted, nil +} + +func (network *cniNetwork) deleteFromNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig) error { + logrus.Infof("About to del CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) + if err := cni.DelNetworkList(context.Background(), network.config, rt); err != nil { logrus.Errorf("Error deleting network: %v", err) return err } return nil } -func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (*libcni.RuntimeConf, error) { +func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string, runtimeConfig RuntimeConfig) (*libcni.RuntimeConf, error) { logrus.Infof("Got pod network %+v", podNetwork) rt := &libcni.RuntimeConf{ @@ -552,9 +807,11 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip str {"K8S_POD_NAME", podNetwork.Name}, {"K8S_POD_INFRA_CONTAINER_ID", podNetwork.ID}, }, + CapabilityArgs: map[string]interface{}{}, } // Add requested static IP to CNI_ARGS + ip := runtimeConfig.IP if ip != "" { if tstIP := net.ParseIP(ip); tstIP == nil { return nil, fmt.Errorf("unable to parse IP address %q", ip) @@ -562,13 +819,26 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip str rt.Args = append(rt.Args, [2]string{"IP", ip}) } - if len(podNetwork.PortMappings) == 0 { - return rt, nil + // Set PortMappings in Capabilities + if len(runtimeConfig.PortMappings) != 0 { + rt.CapabilityArgs["portMappings"] = runtimeConfig.PortMappings } - rt.CapabilityArgs = map[string]interface{}{ - "portMappings": podNetwork.PortMappings, + // Set Bandwidth in Capabilities + if runtimeConfig.Bandwidth != nil { + rt.CapabilityArgs["bandwidth"] = map[string]uint64{ + "ingressRate": runtimeConfig.Bandwidth.IngressRate, + "ingressBurst": runtimeConfig.Bandwidth.IngressBurst, + "egressRate": runtimeConfig.Bandwidth.EgressRate, + "egressBurst": runtimeConfig.Bandwidth.EgressBurst, + } } + + // Set IpRanges in Capabilities + if len(runtimeConfig.IpRanges) > 0 { + rt.CapabilityArgs["ipRanges"] = runtimeConfig.IpRanges + } + return rt, nil } diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go index d760942926..af013aef0a 100644 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go @@ -24,12 +24,44 @@ type PortMapping struct { HostIP string `json:"hostIP"` } -// NetworkConfig is additional configuration for a single CNI network. -type NetworkConfig struct { +// IpRange maps to the standard CNI ipRanges Capability +// see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md +type IpRange struct { + // Subnet is the whole CIDR + Subnet string `json:"subnet"` + // RangeStart is the first available IP in subnet + RangeStart string `json:"rangeStart,omitempty"` + // RangeEnd is the last available IP in subnet + RangeEnd string `json:"rangeEnd,omitempty"` + // Gateway is the gateway of subnet + Gateway string `json:"gateway,omitempty"` +} + +// RuntimeConfig is additional configuration for a single CNI network that +// is pod-specific rather than general to the network. +type RuntimeConfig struct { // IP is a static IP to be specified in the network. Can only be used // with the hostlocal IP allocator. If left unset, an IP will be // dynamically allocated. IP string + // PortMappings is the port mapping of the sandbox. + PortMappings []PortMapping + // Bandwidth is the bandwidth limiting of the pod + Bandwidth *BandwidthConfig + // IpRanges is the ip range gather which is used for address allocation + IpRanges [][]IpRange +} + +// BandwidthConfig maps to the standard CNI bandwidth Capability +// see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md +type BandwidthConfig struct { + // IngressRate is a limit for incoming traffic in bps + IngressRate uint64 + IngressBurst uint64 + + // EgressRate is a limit for outgoing traffic in bps + EgressRate uint64 + EgressBurst uint64 } // PodNetwork configures the network of a pod sandbox. @@ -42,17 +74,34 @@ type PodNetwork struct { ID string // NetNS is the network namespace path of the sandbox. NetNS string - // PortMappings is the port mapping of the sandbox. - PortMappings []PortMapping - // Networks is a list of CNI network names to attach to the sandbox - // Leave this list empty to attach the default network to the sandbox - Networks []string + // Networks is a list of CNI network names (and optional interface + // names) to attach to the sandbox. Leave this list empty to attach the + // default network to the sandbox + Networks []NetAttachment // NetworkConfig is configuration specific to a single CNI network. // It is optional, and can be omitted for some or all specified networks // without issue. - NetworkConfig map[string]NetworkConfig + RuntimeConfig map[string]RuntimeConfig +} + +// NetAttachment describes a container network attachment +type NetAttachment struct { + // NetName contains the name of the CNI network to which the container + // should be or is attached + Name string + // Ifname contains the optional interface name of the attachment + Ifname string +} + +// NetResult contains the result the network attachment operation +type NetResult struct { + // Result is the CNI Result + Result types.Result + // NetAttachment contains the network and interface names of this + // network attachment + NetAttachment } // CNIPlugin is the interface that needs to be implemented by a plugin @@ -68,13 +117,13 @@ type CNIPlugin interface { // SetUpPod is the method called after the sandbox container of // the pod has been created but before the other containers of the // pod are launched. - SetUpPod(network PodNetwork) ([]types.Result, error) + SetUpPod(network PodNetwork) ([]NetResult, error) // TearDownPod is the method called before a pod's sandbox container will be deleted TearDownPod(network PodNetwork) error // Status is the method called to obtain the ipv4 or ipv6 addresses of the pod sandbox - GetPodNetworkStatus(network PodNetwork) ([]types.Result, error) + GetPodNetworkStatus(network PodNetwork) ([]NetResult, error) // NetworkStatus returns error if the network plugin is in error state Status() error diff --git a/vendor/github.com/cri-o/ocicni/vendor.conf b/vendor/github.com/cri-o/ocicni/vendor.conf deleted file mode 100644 index d769d51771..0000000000 --- a/vendor/github.com/cri-o/ocicni/vendor.conf +++ /dev/null @@ -1,13 +0,0 @@ -github.com/containernetworking/cni fbb95fff8a5239a4295c991efa8a397d43118f7e -github.com/fsnotify/fsnotify 1485a34d5d5723fea214f5710708e19a831720e4 -github.com/sirupsen/logrus 787e519fa85519b874dead61020de598e9a23944 -github.com/onsi/ginkgo eea6ad008b96acdaa524f5b409513bf062b500ad -github.com/onsi/gomega 90e289841c1ed79b7a598a7cd9959750cb5e89e2 -golang.org/x/net 63eda1eb0650888965ead1296efd04d0b2b61128 -gopkg.in/yaml.v2 51d6538a90f86fe93ac480b35f37b2be17fef232 -golang.org/x/text e3703dcdd614d2d7488fff034c75c551ea25da95 -golang.org/x/sys f49334f85ddcf0f08d7fb6dd7363e9e6d6b777eb -github.com/hpcloud/tail a1dbeea552b7c8df4b542c66073e393de198a800 -gopkg.in/tomb.v1 dd632973f1e7218eb1089048e0798ec9ae7dceb8 -gopkg.in/fsnotify/fsnotify.v1 c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9 -github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e